#!/usr/local/bin/python3.13
# -*- coding: utf-8 -*-
###############################################################################
#
# Project:  GDAL/OGR samples
# Purpose:  Extract a JPEG file from a JPEG-in-TIFF tile/strip
# Author:   Even Rouault <even dot rouault at spatialys.com>
#
###############################################################################
# Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
#
# SPDX-License-Identifier: MIT
###############################################################################

import sys

from osgeo import gdal


def Usage():
    print("Usage: jpeg_in_tiff_extract.py in.tif out.jpg [tile_x tile_y [band_nbr]]")
    print("")
    print("Extract a JPEG file from a JPEG-in-TIFF tile/strip.")
    print("If tile_x tile_y are not specified, then all tiles/strips are extracted")
    print("in filenames out_[bandnbr_]tx_ty.jpg")
    print("")
    return 2


###############################################################


def extract_tile(ds, src_band_nbr, tile_x, tile_y, jpg_filename):

    block_offset = ds.GetRasterBand(src_band_nbr).GetMetadataItem(
        "BLOCK_OFFSET_%d_%d" % (tile_x, tile_y), "TIFF"
    )
    block_size = ds.GetRasterBand(src_band_nbr).GetMetadataItem(
        "BLOCK_SIZE_%d_%d" % (tile_x, tile_y), "TIFF"
    )
    if block_offset is None or block_size is None:
        print("ERROR: Cannot find block (%d,%d)" % (tile_x, tile_y))
        return 1

    jpegtables = ds.GetRasterBand(src_band_nbr).GetMetadataItem("JPEGTABLES", "TIFF")
    if jpegtables is not None:
        if (
            (len(jpegtables) % 2) != 0
            or jpegtables[0:4] != "FFD8"
            or jpegtables[-2:] != "D9"
        ):
            print("ERROR: Invalid JPEG tables")
            print(jpegtables)
            return 1

        # Remove final D9
        jpegtables = jpegtables[0:-2]

    tiff_f = gdal.VSIFOpenL(ds.GetDescription(), "rb")
    if tiff_f is None:
        print("ERROR: Cannot reopen %s" % ds.GetDescription())
        return 1

    out_f = gdal.VSIFOpenL(jpg_filename, "wb")
    if out_f is None:
        print("ERROR: Cannot create %s" % jpg_filename)
        gdal.VSIFCloseL(tiff_f)
        return 1

    # Write JPEG tables
    if jpegtables is not None:
        for i in range(int(len(jpegtables) / 2)):
            c1 = ord(jpegtables[2 * i])
            c2 = ord(jpegtables[2 * i + 1])
            if c1 >= ord("0") and c1 <= ord("9"):
                val = c1 - ord("0")
            else:
                val = (c1 - ord("A")) + 10
            val = val * 16
            if c2 >= ord("0") and c2 <= ord("9"):
                val = val + (c2 - ord("0"))
            else:
                val = val + (c2 - ord("A")) + 10
            gdal.VSIFWriteL(chr(val), 1, 1, out_f)
    else:
        gdal.VSIFWriteL(chr(0xFF), 1, 1, out_f)
        gdal.VSIFWriteL(chr(0xD8), 1, 1, out_f)

    # Write Adobe APP14 marker if necessary
    interleave = ds.GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE")
    photometric = ds.GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE")
    if interleave == "PIXEL" and photometric == "JPEG" and ds.RasterCount == 3:
        adobe_app14 = [
            0xFF,
            0xEE,
            0x00,
            0x0E,
            0x41,
            0x64,
            0x6F,
            0x62,
            0x65,
            0x00,
            0x64,
            0x00,
            0x00,
            0x00,
            0x00,
            0x00,
        ]
        for c in adobe_app14:
            gdal.VSIFWriteL(chr(c), 1, 1, out_f)

    # Write JPEG codestream
    # skip leading 0xFF 0xD8
    gdal.VSIFSeekL(tiff_f, int(block_offset) + 2, 0)
    data = gdal.VSIFReadL(1, int(block_size) - 2, tiff_f)
    gdal.VSIFCloseL(tiff_f)
    gdal.VSIFWriteL(data, 1, len(data), out_f)

    gdal.VSIFCloseL(out_f)

    aux_xml_filename = "%s.aux.xml" % jpg_filename
    gt = ds.GetGeoTransform()
    srs = ds.GetProjectionRef()
    if srs is not None and srs != "":
        sub_gt = [gt[i] for i in range(6)]
        (blockxsize, blockysize) = ds.GetRasterBand(1).GetBlockSize()
        sub_gt[0] = gt[0] + tile_x * blockxsize * gt[1]
        sub_gt[3] = gt[3] + tile_y * blockysize * gt[5]

        out_f = gdal.VSIFOpenL(aux_xml_filename, "wb")
        if out_f is None:
            print("ERROR: Cannot create %s" % aux_xml_filename)
            return 1
        content = """<PAMDataset>
    <SRS>%s</SRS>
    <GeoTransform>%.18g,%.18g,%.18g,%.18g,%.18g,%.18g</GeoTransform>
    </PAMDataset>
    """ % (
            srs,
            sub_gt[0],
            sub_gt[1],
            sub_gt[2],
            sub_gt[3],
            sub_gt[4],
            sub_gt[5],
        )
        gdal.VSIFWriteL(content, 1, len(content), out_f)
        gdal.VSIFCloseL(out_f)
    else:
        gdal.Unlink("%s.aux.xml" % jpg_filename)

    return 0


###############################################################


def jpeg_in_tiff_extract(argv):

    if len(argv) < 2:
        print("ERROR: Not enough arguments")
        return Usage()

    tiff_filename = argv[0]
    jpg_filename = argv[1]
    if len(argv) >= 3:
        tile_x = int(argv[2])
        tile_y = int(argv[3])
        if len(argv) == 5:
            band_nbr = int(argv[4])
        else:
            band_nbr = None
    else:
        tile_x = None
        tile_y = None

    radix_jpg_filename = jpg_filename
    extensions = [".jpg", ".jpeg", ".JPG", ".JPEG"]
    extension = None
    for ext in extensions:
        pos = radix_jpg_filename.find(ext)
        if pos >= 0:
            extension = ext
            radix_jpg_filename = radix_jpg_filename[0:pos]
            break
    if pos < 0:
        print("ERROR: %s should end with .jpg/.jpeg" % jpg_filename)
        return 1

    ds = gdal.Open(tiff_filename)
    if ds is None:
        print("ERROR: Cannot open %s" % tiff_filename)
        return 1

    if ds.GetDriver() is None or ds.GetDriver().GetDescription() != "GTiff":
        print("ERROR: %s is not a TIFF dataset." % tiff_filename)
        return 1

    photometric = ds.GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE")
    interleave = ds.GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE")

    if photometric != "JPEG" and photometric != "YCbCr JPEG":
        print("ERROR: %s is not a JPEG-compressed TIFF dataset." % tiff_filename)
        return 1

    (blockxsize, blockysize) = ds.GetRasterBand(1).GetBlockSize()
    if blockysize == 1:
        blockysize = ds.RasterYSize
    block_in_row = (ds.RasterXSize + blockxsize - 1) / blockxsize
    block_in_col = (ds.RasterYSize + blockysize - 1) / blockysize

    # Extract single tile ?
    if tile_x is not None:

        if tile_x < 0 or tile_x >= block_in_row:
            print(
                "ERROR: Invalid tile_x : %d. Should be >= 0 and < %d"
                % (tile_x, block_in_row)
            )
            return 1
        if tile_y < 0 or tile_y >= block_in_col:
            print(
                "ERROR: Invalid tile_y : %d. Should be >= 0 and < %d"
                % (tile_y, block_in_col)
            )
            return 1

        if ds.RasterCount > 1:
            if interleave == "PIXEL":
                if band_nbr is not None:
                    print(
                        "ERROR: For a INTERLEAVE=PIXEL dataset, band_nbr should NOT be specified"
                    )
                    return 1
            else:
                if band_nbr is None:
                    print(
                        "ERROR: For a INTERLEAVE=BAND dataset, band_nbr should be specified"
                    )
                    return 1

        if band_nbr is not None:
            if band_nbr < 1 or band_nbr >= ds.RasterCount:
                print(
                    "ERROR: Invalid band_nbr : %d. Should be >= 1 and <= %d"
                    % (tile_y, ds.RasterCount)
                )
                return 1

        if band_nbr is not None:
            src_band_nbr = band_nbr
        else:
            src_band_nbr = 1

        return extract_tile(ds, src_band_nbr, tile_x, tile_y, jpg_filename)

    # Extract all tiles
    else:
        if ds.RasterCount == 1 or interleave == "PIXEL":
            for tile_y in range(block_in_col):
                for tile_x in range(block_in_row):
                    filename = "%s_%d_%d%s" % (
                        radix_jpg_filename,
                        tile_x,
                        tile_y,
                        extension,
                    )
                    ret = extract_tile(ds, 1, tile_x, tile_y, filename)
                    if ret != 0:
                        return ret
        else:
            for src_band_nbr in range(ds.RasterCount):
                for tile_y in range(block_in_col):
                    for tile_x in range(block_in_row):
                        filename = "%s_%d_%d_%d%s" % (
                            radix_jpg_filename,
                            src_band_nbr + 1,
                            tile_x,
                            tile_y,
                            extension,
                        )
                        ret = extract_tile(
                            ds, src_band_nbr + 1, tile_x, tile_y, filename
                        )
                        if ret != 0:
                            return ret
        return 0


def main(argv=sys.argv):
    gdal.GeneralCmdLineProcessor(argv)
    return jpeg_in_tiff_extract(argv[1:])


if __name__ == "__main__":
    sys.exit(main(sys.argv))
