diff --git a/FileFormat.md b/FileFormat.md
index 8e0b7bc7cf9086b72f7829e41de81af7b6b1f86f..0b18d76d8ec039ac77129a2953e157b9ec0366b2 100644
--- a/FileFormat.md
+++ b/FileFormat.md
@@ -54,6 +54,8 @@ Otherwise (codebook per plane), there are always codebook data followed by the p
 ## QCMP File Format V2
 The second iteration of QCMP format solves the problem of the first. Hyperstack dimensions are supported and also metadata can be saved to the compressed file. We have also reserved 19 bytes for the future.
 
+Another difference is that compressed chunk size now also contains the size of the compressed codebook. The data size can now be calculated as a `SUM` of compressed image chunk sizes.
+
 | Offset                   | Size         | Type         | Note                                        |
 | ------------------------ | ------------ | ------------ | ------------------------------------------- |
 | 0                        | 8            | ASCII String | `QCMPFLV2` Magic value                      |
@@ -68,7 +70,7 @@ The second iteration of QCMP format solves the problem of the first. Hyperstack
 | 21                       | 2            | u16          | Vector size X                               |
 | 23                       | 2            | u16          | Vector size Y                               |
 | 25                       | 2            | u16          | Vector size Z                               |
-| 27                       | 4            | u32          | MS=Metadata size                            |
+| 27                       | 4            | i32          | MS=Metadata size                            |
 | 31                       | 19           | u8           | Reserved bytes                              |
 | 50                       | MS           | u8[]         | Metadata                                    |
 | 50 + MS                  | 4*ChunkCount | u32[]        | Size of compressed image chunks             |
diff --git a/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV1.java b/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV1.java
index 71ab8cf60212df16b7db2bd35b1e85ca6be1d0d2..1f31d5817769a61f0249c953ae2514ff0fa514fe 100644
--- a/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV1.java
+++ b/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV1.java
@@ -81,17 +81,36 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
         outputStream.writeShort(vectorSizeZ);
 
         // NOTE(Moravec): Allocate space for plane/voxel layers data sizes. Offset: 23.
-        final int chunkCount = (quantizationType != QuantizationType.Vector3D)
-                ? imageSizeZ
-                : VQImageCompressor.calculateVoxelLayerCount(imageSizeZ, vectorSizeZ);
+        allocateSpaceForChunkSizes(outputStream);
+    }
 
+    /**
+     * Allocate bytes which will be modified at the end. Size of compressed chunks will be saved here.
+     *
+     * @param outputStream Compression stream.
+     * @throws IOException when fails to write int.
+     */
+    protected void allocateSpaceForChunkSizes(final DataOutputStream outputStream) throws IOException {
+        final int chunkCount = getChunkCount();
         for (int i = 0; i < chunkCount; i++) {
             outputStream.writeInt(0x0);
         }
     }
 
+    /**
+     * Get number of compressed image chunks. See FileFormat.md
+     *
+     * @return Number of compressed chunks.
+     */
+    protected int getChunkCount() {
+        return (quantizationType != QuantizationType.Vector3D)
+                ? imageSizeZ
+                : VQImageCompressor.calculateVoxelLayerCount(imageSizeZ, vectorSizeZ);
+    }
+
     @Override
     public void readFromStream(final DataInputStream inputStream) throws IOException {
+        // TODO(Moravec): When we have file inspector the magic value will be read there.
         if (inputStream.available() < BASE_QCMP_HEADER_SIZE) {
             throw new IOException("Provided file is not QCMP file. The file is too small.");
         }
@@ -117,10 +136,17 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
         vectorSizeZ = inputStream.readUnsignedShort();
 
 
-        final int chunkCount = (quantizationType != QuantizationType.Vector3D)
-                ? imageSizeZ
-                : VQImageCompressor.calculateVoxelLayerCount(imageSizeZ, vectorSizeZ);
+        readChunkDataSizes(inputStream);
+    }
 
+    /**
+     * Read sizes of compressed chunks.
+     *
+     * @param inputStream Decompress stream.
+     * @throws IOException when unable to read chunk sizes.
+     */
+    protected void readChunkDataSizes(final DataInputStream inputStream) throws IOException {
+        final int chunkCount = getChunkCount();
         chunkDataSizes = new long[chunkCount];
         for (int i = 0; i < chunkCount; i++) {
             final long readValue = inputStream.readInt();
@@ -358,11 +384,7 @@ public class QCMPFileHeaderV1 implements IFileHeader, Cloneable {
     }
 
     public long getHeaderSize() {
-        final int chunkCount = (quantizationType != QuantizationType.Vector3D)
-                ? imageSizeZ
-                : VQImageCompressor.calculateVoxelLayerCount(imageSizeZ, vectorSizeZ);
-
-        return BASE_QCMP_HEADER_SIZE + (chunkCount * 4);
+        return BASE_QCMP_HEADER_SIZE + (getChunkCount() * 4L);
     }
     //endregion
 }
\ No newline at end of file
diff --git a/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV2.java b/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV2.java
index 63aaf9c34a5e487862322dcf8510580e8770e742..7d9858b7e2665cead27cf290f32fddff4e5b56a1 100644
--- a/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV2.java
+++ b/src/main/java/cz/it4i/qcmp/fileformat/QCMPFileHeaderV2.java
@@ -1,7 +1,7 @@
 package cz.it4i.qcmp.fileformat;
 
 import cz.it4i.qcmp.U16;
-import cz.it4i.qcmp.compression.VQImageCompressor;
+import cz.it4i.qcmp.io.RawDataIO;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -12,6 +12,7 @@ public class QCMPFileHeaderV2 extends QCMPFileHeaderV1 {
     private static final int VERSION = 2;
     private static final int BASE_QCMP_HEADER_SIZE = 50;
     public static final String MAGIC_VALUE = "QCMPFLV2";
+    private static final int RESERVED_BYTES_SIZE = 19;
     //endregion
 
     //region Header fields
@@ -43,12 +44,78 @@ public class QCMPFileHeaderV2 extends QCMPFileHeaderV1 {
 
     @Override
     public void writeToStream(final DataOutputStream outputStream) throws IOException {
-        // TODO
+        outputStream.writeBytes(MAGIC_VALUE);
+
+        outputStream.writeByte(quantizationType.getValue());
+        outputStream.writeByte(bitsPerCodebookIndex);
+        outputStream.writeBoolean(codebookPerPlane);
+
+        outputStream.writeShort(imageSizeX);
+        outputStream.writeShort(imageSizeY);
+        outputStream.writeShort(imageSizeZ);
+        outputStream.writeShort(channelCount);
+        outputStream.writeShort(timepointCount);
+
+        outputStream.writeShort(vectorSizeX);
+        outputStream.writeShort(vectorSizeY);
+        outputStream.writeShort(vectorSizeZ);
+
+        outputStream.writeInt(metadataSize);
+
+        // Reserved bytes.
+        for (int i = 0; i < RESERVED_BYTES_SIZE; i++) {
+            outputStream.writeByte(0x0);
+        }
+        assert (metadata.length == metadataSize) : "Metadata size doesn't match with metadata.length";
+        outputStream.write(metadata);
+
+        // NOTE(Moravec): Allocate space for plane/voxel layers data sizes. Offset: 23.
+        allocateSpaceForChunkSizes(outputStream);
     }
 
     @Override
     public void readFromStream(final DataInputStream inputStream) throws IOException {
-        // TODO
+        if (inputStream.available() < BASE_QCMP_HEADER_SIZE) {
+            throw new IOException("Provided file is not QCMP v2 file. The file is too small.");
+        }
+
+        final byte[] magicValueBuffer = new byte[MAGIC_VALUE.length()];
+        RawDataIO.readFullBuffer(inputStream, magicValueBuffer);
+
+        magicValue = new String(magicValueBuffer);
+        if (!magicValue.equals(MAGIC_VALUE)) {
+            throw new IOException("Provided file is not QCMP v2 file. Magic value is invalid.");
+        }
+
+        quantizationType = QuantizationType.fromByte(inputStream.readByte());
+        bitsPerCodebookIndex = inputStream.readByte();
+        codebookPerPlane = inputStream.readBoolean();
+
+        imageSizeX = inputStream.readUnsignedShort();
+        imageSizeY = inputStream.readUnsignedShort();
+        imageSizeZ = inputStream.readUnsignedShort();
+        channelCount = inputStream.readUnsignedShort();
+        timepointCount = inputStream.readUnsignedShort();
+
+        vectorSizeX = inputStream.readUnsignedShort();
+        vectorSizeY = inputStream.readUnsignedShort();
+        vectorSizeZ = inputStream.readUnsignedShort();
+
+        metadataSize = inputStream.readInt();
+        if (metadataSize < 0)
+            throw new IOException("Negative metadata size was read from stream.");
+
+        final int skipped = inputStream.skipBytes(RESERVED_BYTES_SIZE);
+        if (skipped != RESERVED_BYTES_SIZE)
+            throw new IOException("Unable to read QCMPFileHeaderV2. Unable to skip reserved bytes.");
+
+        // TODO(Moravec): Should we always read the full metadata string?
+        if (metadataSize > 0) {
+            metadata = new byte[metadataSize];
+            RawDataIO.readFullBuffer(inputStream, metadata);
+        }
+
+        readChunkDataSizes(inputStream);
     }
 
     @Override
@@ -88,51 +155,29 @@ public class QCMPFileHeaderV2 extends QCMPFileHeaderV1 {
         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;
+    private long calculateDataSize() {
+        long dataSize = 0;
+        for (final long chunkSize : chunkDataSizes) {
+            dataSize += chunkSize;
         }
+        return dataSize;
+    }
 
-        return (codebookDataSize + totalPlaneDataSize);
+    @Override
+    protected long calculateDataSizeForSq() {
+        return calculateDataSize();
     }
 
     @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);
+        return calculateDataSize();
     }
 
     @Override
     public String getMagicValue() {
         return MAGIC_VALUE;
     }
-
     //endregion
 
     //region Cloneable implementation
@@ -151,8 +196,6 @@ public class QCMPFileHeaderV2 extends QCMPFileHeaderV1 {
     //endregion
 
     //region Getters and Setters
-
-
     public int getChannelCount() {
         return channelCount;
     }
@@ -186,11 +229,7 @@ public class QCMPFileHeaderV2 extends QCMPFileHeaderV1 {
     }
 
     public long getHeaderSize() {
-        final int chunkCount = (quantizationType != QuantizationType.Vector3D)
-                ? imageSizeZ
-                : VQImageCompressor.calculateVoxelLayerCount(imageSizeZ, vectorSizeZ);
-
-        return BASE_QCMP_HEADER_SIZE + metadataSize + (chunkCount * 4L);
+        return BASE_QCMP_HEADER_SIZE + metadataSize + (getChunkCount() * 4L);
     }
     //endregion
 }
\ No newline at end of file