defmodule Scte35 do def run(m3u8_file) do get_scte_string_from(m3u8_file) |> analyze_segmentation_descriptor end def get_scte_string_from(m3u8_file) do {:ok, text} = File.read(m3u8_file) try do text |> String.split("\n") |> Enum.filter(fn line -> line =~ "SCTE35=" end) |> hd |> String.split("SCTE35=") |> tl |> hd |> tap(&IO.puts("SCTE35 string: #{&1}\n")) rescue e -> IO.puts("Invalid SCTE35 string (function 1). " <> e.message) raise ArgumentError, message: "Invalid SCTE35 string (function 1)" end end def analyze_segmentation_descriptor(scte_string) do %{scte_string: scte_string} |> till_segmentation_event_id |> first_2_bytes |> through_component_count |> till_segment_num end def till_segmentation_event_id(map) do try do after_CUEI = map.scte_string |> Base.decode64!() # identifier = "CUEI" |> :binary.split(<<"CUEI">>) |> tl |> hd # pattern match by bytes <> = after_CUEI IO.inspect(segmentation_event_id, label: "segmentation_event_id (decimals)") Map.put(map, :remaining_bytes, rest) rescue e -> IO.puts("Invalid SCTE35 string (function 2). " <> e.message) raise ArgumentError, message: "Invalid SCTE35 string (function 2)" end end def first_2_bytes(map) do IO.puts("\nStart of segmentation_event_cancel_indicator:") IO.inspect(map.remaining_bytes, label: "Remaining Bytes (decimals)") <> = map.remaining_bytes {byte1_decimal, byte2_decimal} = {byte1 |> :binary.first(), byte2 |> :binary.first()} {byte1_binary, byte2_binary} = {byte1_decimal |> base2, byte2_decimal |> base2} IO.puts("\nByte 1: Decimal = #{byte1_decimal}. Binary = #{byte1_binary}") # pattern match by bits <> = byte1 byte1_reserved_hex = byte1_reserved_dec |> base2 |> binary_to_hex IO.puts( "segmentation_event_cancel_indicator (binary): #{segmentation_event_cancel_indicator}" ) IO.puts("reserved (hex): #{byte1_reserved_hex}\n") if segmentation_event_cancel_indicator != 0 do IO.puts("segmentation_event_cancel_indicator wrong. No segmentation_upid or segment_num.") exit(:shutdown) end IO.puts("Byte 2: Decimal = #{byte2_decimal}. Binary = #{byte2_binary}") <> = byte2 byte2_reserved_hex = byte2_reserved_dec |> base2 |> binary_to_hex IO.puts("program_segmentation_flag (binary): #{program_segmentation_flag}") IO.puts("segmentation_duration_flag (binary): #{segmentation_duration_flag}") IO.puts("delivery_not_restricted_flag (binary): #{delivery_not_restricted_flag}") map = if delivery_not_restricted_flag != 0 do IO.puts("reserved (hex): #{byte2_reserved_hex}") Map.put(map, :byte2_reserved, byte2_reserved_hex) else map end Map.merge(map, %{ byte1: byte1_decimal, byte1_reserved: byte1_reserved_hex, byte2: byte2_decimal, segmentation_event_cancel_indicator: segmentation_event_cancel_indicator, program_segmentation_flag: program_segmentation_flag, segmentation_duration_flag: segmentation_duration_flag, delivery_not_restricted_flag: delivery_not_restricted_flag, remaining_bytes: rest }) end def through_component_count(map) do IO.puts("") component_count = map.remaining_bytes |> :binary.first() rest = if map.program_segmentation_flag == 0 && component_count > 0 do IO.puts("component_count (decimal): #{component_count}\n") Map.put(map, :component_count, component_count) total_components_size = 6 * component_count <<_component_bytes::binary-size(total_components_size), rest2::binary>> = map.remaining_bytes rest2 else map.remaining_bytes end Map.put(map, :remaining_bytes, rest) end def till_segment_num(map) do {segmentation_duration, bytes} = if map.segmentation_duration_flag == 1 do <> = map.remaining_bytes segmentation_duration_hex = segmentation_duration_dec |> Base.encode16() IO.puts("segmentation_duration (hex): #{segmentation_duration_hex}") {segmentation_duration_hex, rest} else {"", map.remaining_bytes} end <> = bytes segmentation_upid_length_dec = segmentation_upid_length |> :binary.first() IO.puts("segmentation_upid_type (hex): #{segmentation_upid_type |> Base.encode16()}") IO.puts("segmentation_upid_length (decimal): #{segmentation_upid_length_dec}\n") IO.puts("Start of segmentation_upid:") IO.inspect(rest2, label: "Remaining Bytes (decimals)") map = Map.put(map, :remaining_bytes, rest2) <> = rest2 segment_num_dec = segment_num |> :binary.first() IO.puts("\n--> segmentation_upid: #{segmentation_upid}") IO.puts("--> segment_num (decimal): #{segment_num_dec}\n_____________________________\n") Map.merge(map, %{ segmentation_duration: segmentation_duration, segmentation_upid: segmentation_upid, segment_num: segment_num_dec }) end @spec base2(integer) :: binary def base2(decimal) do Integer.to_string(decimal, 2) |> String.pad_leading(8, "0") end @spec binary_to_hex(binary) :: binary def binary_to_hex(bin) do bin |> Integer.parse(2) |> elem(0) |> Integer.to_string(16) end end