defmodule Membrane.WAV.Postprocessing do @moduledoc """ Module responsible for post-processing serialized WAV files. Due to the fact that `Membrane.WAV.Serializer` with seeking disabled creates WAV file with incorrect `file length` and `data length` blocks in the header, post-processing is needed. `fix_wav_header/1` fixes that problem. Header description can be found in `Membrane.WAV.Parser`. """ @type fix_error :: {:error, File.posix() | :invalid_file | :badarg | :terminated} | IO.nodata() @doc """ Fixes header of the WAV file located in `path`. """ @spec fix_wav_header(String.t()) :: :ok | fix_error() def fix_wav_header(path) do with {:ok, file_length} <- get_file_length(path), {:ok, file} <- File.open(path, [:binary, :read, :write]), {:ok, header_length} <- get_header_length(file), data_length = file_length - header_length, :ok <- update_file(file, file_length, header_length, data_length) do File.close(file) end end defp get_file_length(path) do with {:ok, info} <- File.stat(path) do {:ok, info.size} end end defp get_header_length(file) do with "RIFF" <- IO.binread(file, 4), {:ok, _new_position} <- :file.position(file, 8), <<"WAVE", "fmt ", format_chunk_size::32-little>> <- IO.binread(file, 12), next_chunk_position = 20 + format_chunk_size, {:ok, current_position} <- :file.position(file, next_chunk_position), binary when is_binary(binary) <- IO.binread(file, 8), {:ok, header_length} <- check_fact_and_data(file, binary, current_position) do {:ok, header_length} else binary when is_binary(binary) -> {:error, :invalid_file} error -> error end end defp check_fact_and_data(file, binary, current_position) do case binary do <<"fact", fact_chunk_length::32-little>> -> data_position = current_position + 8 + fact_chunk_length with {:ok, _new_position} <- :file.position(file, data_position), <<"data", _rest::32>> <- IO.binread(file, 8) do {:ok, data_position + 8} end <<"data", _rest::32>> -> {:ok, current_position + 8} _unexpected_data -> {:error, :invalid_file} end end defp update_file(file, file_length, header_length, data_length) do with {:ok, _new_position} <- :file.position(file, 4), # subtracting 8 bytes as `file_length` doesn't include "RIFF" header and the field itself :ok <- IO.binwrite(file, <>), {:ok, _new_position} <- :file.position(file, header_length - 4) do IO.binwrite(file, <>) end end end