Skip to content

FFTHidePNGinWAV

stegobox.codec.FFTHidePNGinWAV

Bases: BaseCodec

This module implements simple FFT steganography hide string in audio(wav). the code refer https://github.com/shalom06/Audio-Stego

Source code in stegobox/codec/fft_hide_png_in_wav.py
class FFTHidePNGinWAV(BaseCodec):
    """
    This module implements simple FFT steganography hide string in audio(wav).
    the code refer https://github.com/shalom06/Audio-Stego
    """

    def __init__(self) -> None:
        super().__init__()

    def encode(
        self, carrier: np.ndarray, payload: np.ndarray
    ) -> tuple[np.ndarray, tuple[int, int, int]]:
        """
        Encoder requires the carrier audio in format WAV and the payload in format png.

        Args:
            carrier: Carrier audio in WAV. Read with `stegobox.io.audio.wavfile_read()`.
            payload: Payload (secret image) to be encoded.

        Returns:
            A tuple representing: (1) The frame information of the audio with the
            payload embedded, and (2) the size of secret image.
        """
        # step 1 divide into chunks
        carrier = carrier.copy()
        image_size = payload.shape
        payload_length = 8 * image_size[0] * image_size[1] * image_size[2]
        blocklength = int(2 * 2 ** np.ceil(np.log2(2 * payload_length)))
        blocknumber = int(np.ceil(carrier.shape[0] / blocklength))
        # checks shape to change data to 1 axis
        if len(carrier.shape) == 1:
            carrier.resize(blocknumber * blocklength, refcheck=False)
            carrier = carrier[np.newaxis]
        else:
            carrier.resize(
                (blocknumber * blocklength, carrier.shape[1]), refcheck=False
            )
            carrier = carrier.T
        blocks = carrier[0].reshape((blocknumber, blocklength))
        # Calculate DFT using fft
        blocks = np.fft.fft(blocks)

        # calculate magnitudes
        magnitudes = np.abs(blocks)

        # create phase matrix
        phases = np.angle(blocks)

        # get phase differences
        phasediffs = np.diff(phases, axis=0)

        # conert message to encode into binary
        payload = payload.reshape(-1)
        textinbinary = np.unpackbits(payload)
        # Convert txt to phase differences
        textinpi = textinbinary.copy()
        textinpi[textinpi == 0] = -1
        textinpi = textinpi * -np.pi / 2

        blockmid = blocklength // 2

        # do phase conversion
        phases[0, blockmid - payload_length : blockmid] = textinpi
        phases[0, blockmid + 1 : blockmid + 1 + payload_length] = -textinpi[::-1]

        # re compute  the ophase amtrix
        for i in range(1, len(phases)):
            phases[i] = phases[i - 1] + phasediffs[i - 1]

        # apply i-dft
        blocks = magnitudes * np.exp(1j * phases)
        blocks = np.fft.ifft(blocks).real

        # combining all block of audio again
        carrier[0] = blocks.ravel().astype(np.int16)

        return carrier.T, (image_size[0], image_size[1], image_size[2])

    def decode(self, _):
        raise NotImplementedError("This codec does not support decoding without length")

    def decode_with_length(
        self, carrier: np.ndarray, image_size: tuple[int, int, int]
    ) -> np.ndarray:
        """
        Decode the secret image from the carrier audio.

        Args:
            carrier: Carrier audio in WAV. Read with `stegobox.io.audio.wavfile_read()`.
            image_size: the size of secret iamge in format [width, height, channels].

        Returns:
            The decoded secret image.
        """

        payload_length = image_size[0] * image_size[1] * image_size[2] * 8
        blocklength = 2 * int(2 ** np.ceil(np.log2(2 * payload_length)))
        blockmid = blocklength // 2

        # get header info
        if len(carrier.shape) == 1:
            secret = carrier[:blocklength]
        else:
            secret = carrier[:blocklength, 0]

        # get the phase and convert it to binary
        secretphases = np.angle(np.fft.fft(secret))[
            blockmid - payload_length : blockmid
        ]
        secretbinary = (secretphases < 0).astype(np.int8)

        #  convert into characters
        secretincode = secretbinary.reshape((-1, 8)).dot(1 << np.arange(8 - 1, -1, -1))
        outdata = secretincode.reshape(image_size)
        return outdata

encode(carrier, payload)

Encoder requires the carrier audio in format WAV and the payload in format png.

Parameters:

Name Type Description Default
carrier ndarray

Carrier audio in WAV. Read with stegobox.io.audio.wavfile_read().

required
payload ndarray

Payload (secret image) to be encoded.

required

Returns:

Type Description
ndarray

A tuple representing: (1) The frame information of the audio with the

tuple[int, int, int]

payload embedded, and (2) the size of secret image.

Source code in stegobox/codec/fft_hide_png_in_wav.py
def encode(
    self, carrier: np.ndarray, payload: np.ndarray
) -> tuple[np.ndarray, tuple[int, int, int]]:
    """
    Encoder requires the carrier audio in format WAV and the payload in format png.

    Args:
        carrier: Carrier audio in WAV. Read with `stegobox.io.audio.wavfile_read()`.
        payload: Payload (secret image) to be encoded.

    Returns:
        A tuple representing: (1) The frame information of the audio with the
        payload embedded, and (2) the size of secret image.
    """
    # step 1 divide into chunks
    carrier = carrier.copy()
    image_size = payload.shape
    payload_length = 8 * image_size[0] * image_size[1] * image_size[2]
    blocklength = int(2 * 2 ** np.ceil(np.log2(2 * payload_length)))
    blocknumber = int(np.ceil(carrier.shape[0] / blocklength))
    # checks shape to change data to 1 axis
    if len(carrier.shape) == 1:
        carrier.resize(blocknumber * blocklength, refcheck=False)
        carrier = carrier[np.newaxis]
    else:
        carrier.resize(
            (blocknumber * blocklength, carrier.shape[1]), refcheck=False
        )
        carrier = carrier.T
    blocks = carrier[0].reshape((blocknumber, blocklength))
    # Calculate DFT using fft
    blocks = np.fft.fft(blocks)

    # calculate magnitudes
    magnitudes = np.abs(blocks)

    # create phase matrix
    phases = np.angle(blocks)

    # get phase differences
    phasediffs = np.diff(phases, axis=0)

    # conert message to encode into binary
    payload = payload.reshape(-1)
    textinbinary = np.unpackbits(payload)
    # Convert txt to phase differences
    textinpi = textinbinary.copy()
    textinpi[textinpi == 0] = -1
    textinpi = textinpi * -np.pi / 2

    blockmid = blocklength // 2

    # do phase conversion
    phases[0, blockmid - payload_length : blockmid] = textinpi
    phases[0, blockmid + 1 : blockmid + 1 + payload_length] = -textinpi[::-1]

    # re compute  the ophase amtrix
    for i in range(1, len(phases)):
        phases[i] = phases[i - 1] + phasediffs[i - 1]

    # apply i-dft
    blocks = magnitudes * np.exp(1j * phases)
    blocks = np.fft.ifft(blocks).real

    # combining all block of audio again
    carrier[0] = blocks.ravel().astype(np.int16)

    return carrier.T, (image_size[0], image_size[1], image_size[2])

decode_with_length(carrier, image_size)

Decode the secret image from the carrier audio.

Parameters:

Name Type Description Default
carrier ndarray

Carrier audio in WAV. Read with stegobox.io.audio.wavfile_read().

required
image_size tuple[int, int, int]

the size of secret iamge in format [width, height, channels].

required

Returns:

Type Description
ndarray

The decoded secret image.

Source code in stegobox/codec/fft_hide_png_in_wav.py
def decode_with_length(
    self, carrier: np.ndarray, image_size: tuple[int, int, int]
) -> np.ndarray:
    """
    Decode the secret image from the carrier audio.

    Args:
        carrier: Carrier audio in WAV. Read with `stegobox.io.audio.wavfile_read()`.
        image_size: the size of secret iamge in format [width, height, channels].

    Returns:
        The decoded secret image.
    """

    payload_length = image_size[0] * image_size[1] * image_size[2] * 8
    blocklength = 2 * int(2 ** np.ceil(np.log2(2 * payload_length)))
    blockmid = blocklength // 2

    # get header info
    if len(carrier.shape) == 1:
        secret = carrier[:blocklength]
    else:
        secret = carrier[:blocklength, 0]

    # get the phase and convert it to binary
    secretphases = np.angle(np.fft.fft(secret))[
        blockmid - payload_length : blockmid
    ]
    secretbinary = (secretphases < 0).astype(np.int8)

    #  convert into characters
    secretincode = secretbinary.reshape((-1, 8)).dot(1 << np.arange(8 - 1, -1, -1))
    outdata = secretincode.reshape(image_size)
    return outdata