diff --git a/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV1.java b/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV1.java
index f97fbf4581fbbc6af44ce6e67df3cdcd33cedf66..71ab8cf60212df16b7db2bd35b1e85ca6be1d0d2 100644
--- a/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV1.java
+++ b/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV1.java
@@ -44,7 +44,7 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
      */
     @Override
     public boolean validateHeader() {
-        if (!magicValue.equals(MAGIC_VALUE))
+        if (!magicValue.equals(getMagicValue()))
             return false;
 
         if (bitsPerCodebookIndex == 0)
@@ -139,26 +139,10 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
             builder.append("Header is:\t\t invalid\n");
             return;
         }
-        builder.append("HeaderVersion\t\t: ").append(VERSION).append('\n');
-        builder.append("Magic value\t\t: ").append(magicValue).append('\n');
-        builder.append("Quantization type\t: ");
-        switch (quantizationType) {
-            case Scalar:
-                builder.append("Scalar\n");
-                break;
-            case Vector1D:
-                builder.append("Vector1D\n");
-                break;
-            case Vector2D:
-                builder.append("Vector2D\n");
-                break;
-            case Vector3D:
-                builder.append("Vector3D\n");
-                break;
-            case Invalid:
-                builder.append("INVALID\n");
-                break;
-        }
+        builder.append("HeaderVersion\t\t: ").append(getHeaderVersion()).append('\n');
+        builder.append("Magic value\t\t: ").append(getMagicValue()).append('\n');
+        builder.append("Quantization type\t: ").append(quantizationType).append('\n');
+
         builder.append("Bits per pixel\t\t: ").append(bitsPerCodebookIndex).append('\n');
 
         builder.append("Codebook\t\t: ").append(codebookPerPlane ? "one per plane\n" : "one for all\n");
@@ -176,6 +160,10 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
                 .append(vectorSizeY).append('x')
                 .append(vectorSizeZ).append('\n');
 
+        printFileSizeInfo(builder, inputFile);
+    }
+
+    protected void printFileSizeInfo(final StringBuilder builder, final String inputFile) {
         final long headerSize = getHeaderSize();
         final long fileSize = new File(inputFile).length();
         final long dataSize = fileSize - headerSize;
@@ -190,7 +178,7 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
         builder.append("Data size\t\t: ");
         Utils.prettyPrintFileSize(builder, dataSize).append(correctFileSize ? "(correct)\n" : "(INVALID)\n");
 
-        final long pixelCount = imageSizeX * imageSizeY * imageSizeZ;
+        final long pixelCount = (long) imageSizeX * imageSizeY * imageSizeZ;
         final long uncompressedSize = 2 * pixelCount; // We assert 16 bit (2 byte) pixel.
         final double compressionRatio = (double) fileSize / (double) uncompressedSize;
         builder.append(String.format("Compression ratio\t: %.4f\n", compressionRatio));
@@ -201,7 +189,7 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
         builder.append("\n=== Input file is ").append(correctFileSize ? "VALID" : "INVALID").append(" ===\n");
     }
 
-    private long calculateDataSizeForSq() {
+    protected long calculateDataSizeForSq() {
         final int LONG_BYTES = 8;
         // Quantization value count.
         final int codebookSize = (int) Math.pow(2, bitsPerCodebookIndex);
@@ -218,7 +206,7 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
         return (codebookDataSize + totalPlaneDataSize);
     }
 
-    private long calculateDataSizeForVq() {
+    protected long calculateDataSizeForVq() {
         final int LONG_BYTES = 8;
         // Vector count in codebook
         final int codebookSize = (int) Math.pow(2, bitsPerCodebookIndex);
@@ -252,7 +240,7 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
 
     @Override
     public String getMagicValue() {
-        return magicValue;
+        return MAGIC_VALUE;
     }
 
     //endregion
diff --git a/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV2.java b/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV2.java
new file mode 100644
index 0000000000000000000000000000000000000000..63aaf9c34a5e487862322dcf8510580e8770e742
--- /dev/null
+++ b/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV2.java
@@ -0,0 +1,196 @@
+package cz.it4i.qcmp.fileformat;
+
+import cz.it4i.qcmp.U16;
+import cz.it4i.qcmp.compression.VQImageCompressor;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class QCMPFileHeaderV2 extends QCMPFileHeaderV1 {
+    //region Constants
+    private static final int VERSION = 2;
+    private static final int BASE_QCMP_HEADER_SIZE = 50;
+    public static final String MAGIC_VALUE = "QCMPFLV2";
+    //endregion
+
+    //region Header fields
+    private int channelCount;
+    private int timepointCount;
+    private int metadataSize;
+    private byte[] metadata;
+    //endregion
+
+    //region IFileHeader implementation
+
+    /**
+     * Validate that all header values are in their valid range.
+     *
+     * @return True if this is valid QCMPFILE header.
+     */
+    @Override
+    public boolean validateHeader() {
+        if (!super.validateHeader())
+            return false;
+
+        if (!U16.isInRange(channelCount))
+            return false;
+        if (!U16.isInRange(timepointCount))
+            return false;
+
+        return metadataSize >= 0;
+    }
+
+    @Override
+    public void writeToStream(final DataOutputStream outputStream) throws IOException {
+        // TODO
+    }
+
+    @Override
+    public void readFromStream(final DataInputStream inputStream) throws IOException {
+        // TODO
+    }
+
+    @Override
+    public int getHeaderVersion() {
+        return VERSION;
+    }
+
+    @Override
+    public void report(final StringBuilder builder, final String inputFile) {
+        if (!validateHeader()) {
+            builder.append("Header is:\t\t invalid\n");
+            return;
+        }
+        builder.append("HeaderVersion\t\t: ").append(getHeaderVersion()).append('\n');
+        builder.append("Magic value\t\t: ").append(getMagicValue()).append('\n');
+        builder.append("Quantization type\t: ").append(quantizationType).append('\n');
+        builder.append("Bits per pixel\t\t: ").append(bitsPerCodebookIndex).append('\n');
+        builder.append("Codebook\t\t: ").append(codebookPerPlane ? "one per plane\n" : "one for all\n");
+
+        final int codebookSize = (int) Math.pow(2, bitsPerCodebookIndex);
+        builder.append("Codebook size\t\t: ").append(codebookSize).append('\n');
+
+        builder.append("Image stack size\t: ")
+                .append(imageSizeX).append('x')
+                .append(imageSizeY).append('x')
+                .append(imageSizeZ).append('x')
+                .append(channelCount).append('x')
+                .append(timepointCount).append(" [XxYxZxCxT]").append('\n');
+
+        builder.append("Quantization vector\t: ")
+                .append(vectorSizeX).append('x')
+                .append(vectorSizeY).append('x')
+                .append(vectorSizeZ).append('\n');
+
+        builder.append("MetadataSize\t: ").append(metadataSize).append('\n');
+
+        printFileSizeInfo(builder, inputFile);
+    }
+
+    @Override
+    protected long calculateDataSizeForSq() {
+        // TODO(Moravec): Fix this calculation. Size of the huffman tree will be added to chunk size.
+        final int LONG_BYTES = 8;
+        // Quantization value count.
+        final int codebookSize = (int) Math.pow(2, bitsPerCodebookIndex);
+
+        // Total codebook size in bytes. Also symbol frequencies for Huffman.
+        final long codebookDataSize = ((2 * codebookSize) + (LONG_BYTES * codebookSize)) * (codebookPerPlane ? imageSizeZ : 1);
+
+        // Indices are encoded using huffman. Plane data size is written in the header.
+        long totalPlaneDataSize = 0;
+        for (final long planeDataSize : planeDataSizes) {
+            totalPlaneDataSize += planeDataSize;
+        }
+
+        return (codebookDataSize + totalPlaneDataSize);
+    }
+
+    @Override
+    protected long calculateDataSizeForVq() {
+        // TODO(Moravec): Fix this calculation. Size of the huffman tree will be added to chunk size.
+        final int LONG_BYTES = 8;
+        // Vector count in codebook
+        final int codebookSize = (int) Math.pow(2, bitsPerCodebookIndex);
+
+        // Single vector size in bytes.
+        final int vectorDataSize = 2 * vectorSizeX * vectorSizeY * vectorSizeZ;
+
+        // Total codebook size in bytes.
+        final long codebookDataSize = ((codebookSize * vectorDataSize) + (codebookSize * LONG_BYTES)) * (codebookPerPlane ? imageSizeZ : 1);
+
+        // Indices are encoded using huffman. Plane data size is written in the header.
+        long totalPlaneDataSize = 0;
+        for (final long planeDataSize : planeDataSizes) {
+            totalPlaneDataSize += planeDataSize;
+        }
+        return (codebookDataSize + totalPlaneDataSize);
+    }
+
+    @Override
+    public String getMagicValue() {
+        return MAGIC_VALUE;
+    }
+
+    //endregion
+
+    //region Cloneable implementation
+    @Override
+    protected Object clone() throws CloneNotSupportedException {
+        return super.clone();
+    }
+
+    public QCMPFileHeaderV2 copyOf() {
+        try {
+            return (QCMPFileHeaderV2) this.clone();
+        } catch (final CloneNotSupportedException e) {
+            return null;
+        }
+    }
+    //endregion
+
+    //region Getters and Setters
+
+
+    public int getChannelCount() {
+        return channelCount;
+    }
+
+    public void setChannelCount(final int channelCount) {
+        this.channelCount = channelCount;
+    }
+
+    public int getTimepointCount() {
+        return timepointCount;
+    }
+
+    public void setTimepointCount(final int timepointCount) {
+        this.timepointCount = timepointCount;
+    }
+
+    public int getMetadataSize() {
+        return metadataSize;
+    }
+
+    public void setMetadataSize(final int metadataSize) {
+        this.metadataSize = metadataSize;
+    }
+
+    public byte[] getMetadata() {
+        return metadata;
+    }
+
+    public void setMetadata(final byte[] metadata) {
+        this.metadata = metadata;
+    }
+
+    public long getHeaderSize() {
+        final int chunkCount = (quantizationType != QuantizationType.Vector3D)
+                ? imageSizeZ
+                : VQImageCompressor.calculateVoxelLayerCount(imageSizeZ, vectorSizeZ);
+
+        return BASE_QCMP_HEADER_SIZE + metadataSize + (chunkCount * 4L);
+    }
+    //endregion
+}
\ No newline at end of file