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
<<segmentation_event_id::binary-size(4), rest::binary>> = 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)")
<<byte1::binary-size(1), byte2::binary-size(1), rest::binary>> = 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
<<segmentation_event_cancel_indicator::1, byte1_reserved_dec::7>> = 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}")
<<program_segmentation_flag::1, segmentation_duration_flag::1,
delivery_not_restricted_flag::1, byte2_reserved_dec::5>> = 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
<<segmentation_duration_dec::binary-size(5), rest::binary>> = 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
<<segmentation_upid_type::binary-size(1), segmentation_upid_length::binary-size(1),
rest2::binary>> = 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)
<<segmentation_upid::binary-size(segmentation_upid_length_dec),
_segmentation_type_id::binary-size(1), segment_num::binary-size(1), _rest3::binary>> = 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