{"id":1616,"date":"2026-02-14T20:48:05","date_gmt":"2026-02-14T20:48:05","guid":{"rendered":"https:\/\/radzishevsky.com\/blog\/?p=1616"},"modified":"2026-02-14T20:48:05","modified_gmt":"2026-02-14T20:48:05","slug":"wav-file-integrity-test-script-python","status":"publish","type":"post","link":"https:\/\/radzishevsky.com\/blog\/wav-file-integrity-test-script-python\/","title":{"rendered":".WAV file integrity test script (Python)"},"content":{"rendered":"\n<p>Analyzes .wav file structure, reports any inconsistencies.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code># ======================================\n# .WAV file structure integrity test\n# Alex Radzishevsky\n# www.radzishevsky.com\n# February 2026\n# ======================================\n\nimport struct\nimport os\nimport sys\n\ndef get_audio_format(code):\n    formats = {\n        0x0001: \"PCM (Integer)\",\n        0x0003: \"IEEE Float\",\n        0x0006: \"ALAW\",\n        0x0007: \"MULAW\",\n        0xFFFE: \"WAVE_FORMAT_EXTENSIBLE\"\n    }\n    return formats.get(code, f\"Unknown\/Compressed (0x{code:04x})\")\n\ndef hex_dump(data, length=64):\n    res = &#91;]\n    for i in range(0, min(len(data), length), 16):\n        chunk = data&#91;i:i+16]\n        hex_str = \" \".join(f\"{b:02x}\" for b in chunk)\n        ascii_str = \"\".join(chr(b) if 32 &lt;= b &lt;= 126 else \".\" for b in chunk)\n        res.append(f\"  {i:04x}:  {hex_str:&lt;48}  |{ascii_str}|\")\n    return \"\\n\".join(res)\n\ndef inspect_wav_comprehensive(file_path):\n    if not os.path.exists(file_path):\n        print(f\"Error: File '{file_path}' not found.\")\n        return\n\n    file_size = os.path.getsize(file_path)\n    print(f\"\\n{'='*85}\")\n    print(f\" ANALYSIS REPORT: {os.path.basename(file_path)}\")\n    print(f\" SYSTEM FILE SIZE: {file_size} bytes\")\n    print(f\"{'='*85}\\n\")\n\n    with open(file_path, 'rb') as f:\n        # --- 1. INITIAL HEX DUMP ---\n        print(\"&#91;01] HEADER HEX PREVIEW (First 64 Bytes)\")\n        print(hex_dump(f.read(64)))\n        f.seek(0)\n        print(\"-\" * 85)\n\n        # --- 2. GLOBAL RIFF HEADER ---\n        header = f.read(12)\n        if len(header) &lt; 12:\n            print(\"&#91;CRITICAL ERROR] File is smaller than a 12-byte RIFF header. Investigation aborted.\")\n            return\n\n        tag, r_size, w_tag = struct.unpack('&lt;4sI4s', header)\n        expected_riff_end = 8 + r_size\n        \n        print(f\"&#91;02] GLOBAL CONTAINER\")\n        print(f\"  Container ID:    {tag.decode(errors='ignore')} (Expected: RIFF)\")\n        print(f\"  Declared Size:   {r_size} bytes\")\n        print(f\"  Format Type:     {w_tag.decode(errors='ignore')} (Expected: WAVE)\")\n        \n        if r_size != file_size - 8:\n            diff = (file_size - 8) - r_size\n            status = \"TRAILING DATA\" if diff > 0 else \"TRUNCATED\"\n            print(f\"  !! INTEGRITY ALERT: Size mismatch. System reports {file_size-8}, Header reports {r_size}.\")\n            print(f\"     File status appears: {status} by {abs(diff)} bytes.\")\n        else:\n            print(\"  Structure: Global header matches system file size.\")\n        print(\"-\" * 85)\n\n        # --- 3. CHUNK ITERATOR ---\n        print(\"&#91;03] CHUNK EXPLORER\")\n        mandatory = {'fmt ': False, 'data': False}\n        audio_params = {}\n\n        while f.tell() &lt; file_size:\n            pos = f.tell()\n            \n            # Boundary Check\n            if file_size - pos &lt; 8:\n                print(f\"\\n  &#91;!] TRAILING DATA: Found {file_size - pos} unexplained bytes at offset {pos}.\")\n                break\n\n            c_id_raw, c_size = struct.unpack('&lt;4sI', f.read(8))\n            c_id = c_id_raw.decode('ascii', errors='ignore').strip()\n            \n            print(f\"\\n  CHUNK: &#91;{c_id:&lt;4}] | Offset: {pos:&lt;8} | Size: {c_size} bytes\")\n\n            # Validate chunk doesn't overrun file\n            if f.tell() + c_size > file_size:\n                missing = (f.tell() + c_size) - file_size\n                print(f\"  &#91;CRITICAL] Error: Chunk &#91;{c_id}] is truncated. {missing} bytes are missing from the file.\")\n                break\n\n            # Process Specific Chunks\n            if c_id == 'fmt':\n                mandatory&#91;'fmt '] = True\n                fmt_data = f.read(c_size)\n                a_fmt, n_ch, s_rate, b_rate, b_align, bps = struct.unpack('&lt;HHIIHH', fmt_data&#91;:16])\n                audio_params = {'s_rate': s_rate, 'n_ch': n_ch, 'bps': bps}\n                \n                print(f\"    > Format Category: {get_audio_format(a_fmt)}\")\n                print(f\"    > Channels:        {n_ch}\")\n                print(f\"    > Sample Rate:     {s_rate} Hz\")\n                print(f\"    > Bits Per Sample: {bps} bit\")\n                print(f\"    > Byte Rate:       {b_rate} (Calculated: {s_rate * n_ch * (bps \/\/ 8)})\")\n                print(f\"    > Block Align:     {b_align} (Calculated: {n_ch * (bps \/\/ 8)})\")\n                \n                if b_rate != s_rate * n_ch * (bps \/\/ 8):\n                    print(\"    !! ERROR: Byte Rate calculation is incorrect.\")\n                \n                if a_fmt == 0xFFFE and len(fmt_data) >= 40:\n                    v_bps, mask = struct.unpack('&lt;HI', fmt_data&#91;18:24])\n                    print(f\"    > Extensible Info: Valid Bits: {v_bps}, Channel Mask: {hex(mask)}\")\n\n            elif c_id == 'data':\n                mandatory&#91;'data'] = True\n                print(f\"    > Content: Raw Audio Payload.\")\n                if audio_params:\n                    duration = c_size \/ (audio_params&#91;'s_rate'] * audio_params&#91;'n_ch'] * (audio_params&#91;'bps'] \/ 8))\n                    print(f\"    > Calculated Audio Length: {duration:.3f} seconds\")\n                f.seek(c_size, 1)\n\n            elif c_id == 'LIST':\n                l_type = f.read(4).decode(errors='ignore')\n                print(f\"    > List Type: {l_type}\")\n                if l_type == 'INFO':\n                    rem = c_size - 4\n                    while rem > 8:\n                        m_id, m_sz = struct.unpack('&lt;4sI', f.read(8))\n                        m_val = f.read(m_sz).decode('ascii', errors='ignore').strip('\\x00')\n                        print(f\"      - {m_id.decode(errors='ignore')}: {m_val}\")\n                        rem -= (8 + m_sz)\n                        if m_sz % 2 != 0: \n                            f.read(1); rem -= 1\n                else:\n                    f.seek(c_size - 4, 1)\n\n            else:\n                print(f\"    > Action: Skipping unhandled chunk.\")\n                f.seek(c_size, 1)\n\n            # Padding Verification\n            if c_size % 2 != 0:\n                if f.tell() &lt; file_size:\n                    pad = f.read(1)\n                    if pad != b'\\x00':\n                        print(f\"    &#91;!] Warning: Padding byte at {f.tell()-1} is non-zero (0x{pad.hex()}).\")\n                else:\n                    print(f\"    &#91;!] Warning: Missing padding byte for odd-sized chunk at end of file.\")\n\n        # --- 4. FINAL SUMMARY ---\n        print(\"\\n\" + \"-\" * 85)\n        print(\"&#91;04] FINAL VERDICT\")\n        missing_mandatory = &#91;k for k, v in mandatory.items() if not v]\n        if missing_mandatory:\n            print(f\"  FAILED: Missing mandatory chunks: {missing_mandatory}\")\n        elif f.tell() == file_size:\n            print(\"  SUCCESS: RIFF structure is clean and perfectly aligned.\")\n        else:\n            print(f\"  COMPLETE: Structure parsed, but identified integrity warnings (see above).\")\n        print(\"=\" * 85 + \"\\n\")\n\nif __name__ == \"__main__\":\n    if len(sys.argv) &lt; 2:\n        print(\"Usage: python inspect_wav.py &lt;filename.wav>\")\n    else:\n        inspect_wav_comprehensive(sys.argv&#91;1])<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code><\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Analyzes .wav file structure, reports any inconsistencies.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[143,145,127],"tags":[],"class_list":["post-1616","post","type-post","status-publish","format-standard","hentry","category-programming","category-tech_hacks","category-technology"],"_links":{"self":[{"href":"https:\/\/radzishevsky.com\/blog\/wp-json\/wp\/v2\/posts\/1616","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/radzishevsky.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/radzishevsky.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/radzishevsky.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/radzishevsky.com\/blog\/wp-json\/wp\/v2\/comments?post=1616"}],"version-history":[{"count":1,"href":"https:\/\/radzishevsky.com\/blog\/wp-json\/wp\/v2\/posts\/1616\/revisions"}],"predecessor-version":[{"id":1617,"href":"https:\/\/radzishevsky.com\/blog\/wp-json\/wp\/v2\/posts\/1616\/revisions\/1617"}],"wp:attachment":[{"href":"https:\/\/radzishevsky.com\/blog\/wp-json\/wp\/v2\/media?parent=1616"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/radzishevsky.com\/blog\/wp-json\/wp\/v2\/categories?post=1616"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/radzishevsky.com\/blog\/wp-json\/wp\/v2\/tags?post=1616"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}