-module(glubs@webvtt). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]). -export([to_string/1, parse/1, tokenize/1]). -export_type([item/0, web_vtt/0, token/0, tokenization_error/0]). -type item() :: {note, binary()} | {cue, gleam@option:option(binary()), integer(), integer(), binary()}. -type web_vtt() :: {web_vtt, gleam@option:option(binary()), list(item())}. -type token() :: {start_tag, binary(), list(binary()), gleam@option:option(binary())} | {text, binary()} | {timestamp, integer()} | {end_tag, binary()}. -type tokenization_error() :: invalid_start_token | invalid_end_token. -spec header_to_string(gleam@option:option(binary())) -> gleam@string_builder:string_builder(). header_to_string(Comment) -> case Comment of {some, Comment@1} -> gleam@string_builder:from_strings([<<" "/utf8>>, Comment@1]); none -> gleam@string_builder:new() end. -spec item_to_string(item()) -> gleam@string_builder:string_builder(). item_to_string(Item) -> case Item of {note, Content} -> case gleam_stdlib:contains_string(Content, <<"\n"/utf8>>) of true -> gleam@string_builder:from_strings( [<<"NOTE\n"/utf8>>, Content] ); false -> gleam@string_builder:from_strings( [<<"NOTE "/utf8>>, Content] ) end; {cue, Id, Start_time, End_time, Payload} -> Start_time@1 = glubs@timestamp:to_string(Start_time, <<"."/utf8>>), End_time@1 = glubs@timestamp:to_string(End_time, <<"."/utf8>>), Timestamp = begin _pipe = Start_time@1, _pipe@1 = gleam@string_builder:append(_pipe, <<" --> "/utf8>>), gleam@string_builder:append_builder(_pipe@1, End_time@1) end, _pipe@4 = case Id of {some, Id@1} -> _pipe@2 = gleam@string_builder:from_string(Id@1), _pipe@3 = gleam@string_builder:append( _pipe@2, <<"\n"/utf8>> ), gleam@string_builder:append_builder(_pipe@3, Timestamp); none -> Timestamp end, _pipe@5 = gleam@string_builder:append(_pipe@4, <<"\n"/utf8>>), gleam@string_builder:append(_pipe@5, Payload) end. -spec items_to_string(list(item())) -> gleam@string_builder:string_builder(). items_to_string(Items) -> _pipe = Items, _pipe@1 = gleam@list:map(_pipe, fun item_to_string/1), gleam@string_builder:join(_pipe@1, <<"\n\n"/utf8>>). -spec to_string(web_vtt()) -> binary(). to_string(Webvtt) -> {web_vtt, Comment, Items} = case Webvtt of {web_vtt, _, _} -> Webvtt; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"glubs/webvtt"/utf8>>, function => <<"to_string"/utf8>>, line => 38}) end, _pipe = <<"WEBVTT"/utf8>>, _pipe@1 = gleam@string_builder:from_string(_pipe), _pipe@2 = gleam@string_builder:append_builder( _pipe@1, header_to_string(Comment) ), _pipe@3 = gleam@string_builder:append(_pipe@2, <<"\n\n"/utf8>>), _pipe@4 = gleam@string_builder:append_builder( _pipe@3, items_to_string(Items) ), _pipe@5 = gleam@string_builder:append(_pipe@4, <<"\n"/utf8>>), gleam@string_builder:to_string(_pipe@5). -spec parse_comment(binary()) -> {ok, gleam@option:option(binary())} | {error, binary()}. parse_comment(Header) -> case Header of <<"WEBVTT"/utf8>> -> {ok, none}; <<"WEBVTT\t"/utf8, Comment/binary>> -> {ok, {some, Comment}}; <<"WEBVTT "/utf8, Comment@1/binary>> -> {ok, {some, Comment@1}}; <<"WEBVTT"/utf8, _/binary>> -> {error, <<"Header comment must start with space or tab"/utf8>>}; _ -> {error, <<"Must start with \"WEBVTT\""/utf8>>} end. -spec parse_note(binary()) -> {ok, item()} | {error, binary()}. parse_note(Note) -> case Note of <<"NOTE\n"/utf8, Note@1/binary>> -> {ok, {note, Note@1}}; <<"NOTE "/utf8, Note@2/binary>> -> {ok, {note, Note@2}}; _ -> {error, <<"Invalid note"/utf8>>} end. -spec parse_cue_id(binary()) -> {ok, {gleam@option:option(binary()), binary()}} | {error, binary()}. parse_cue_id(Cue) -> case gleam@string:split_once(Cue, <<"\n"/utf8>>) of {ok, {Id, Rest}} -> case gleam_stdlib:contains_string(Id, <<"-->"/utf8>>) of true -> {ok, {none, Cue}}; false -> {ok, {{some, Id}, Rest}} end; {error, nil} -> {error, <<"Invalid cue"/utf8>>} end. -spec parse_cue(binary()) -> {ok, item()} | {error, binary()}. parse_cue(Cue) -> gleam@result:'try'( parse_cue_id(Cue), fun(_use0) -> {Id, Rest} = _use0, case gleam@string:split_once(Rest, <<"\n"/utf8>>) of {ok, {Line, Payload}} -> gleam@result:'try'( glubs@timestamp:parse_range(Line, <<"."/utf8>>), fun(_use0@1) -> {Start, End} = _use0@1, {ok, {cue, Id, Start, End, Payload}} end ); {error, nil} -> {error, <<"Invalid cue"/utf8>>} end end ). -spec parse_item(binary()) -> {ok, item()} | {error, binary()}. parse_item(Item) -> _pipe = Item, _pipe@1 = parse_note(_pipe), gleam@result:try_recover(_pipe@1, fun(_) -> parse_cue(Item) end). -spec parse(binary()) -> {ok, web_vtt()} | {error, binary()}. parse(Webvtt) -> [Header | Body] = begin _pipe = Webvtt, _pipe@1 = gleam@string:replace(_pipe, <<"\r\n"/utf8>>, <<"\n"/utf8>>), _pipe@2 = gleam@string:trim_right(_pipe@1), gleam@string:split(_pipe@2, <<"\n\n"/utf8>>) end, [Header@1 | _] = gleam@string:split(Header, <<"\n"/utf8>>), gleam@result:'try'( parse_comment(Header@1), fun(Comment) -> gleam@result:'try'( gleam@list:try_map(Body, fun parse_item/1), fun(Items) -> {ok, {web_vtt, Comment, Items}} end ) end ). -spec parse_tag_and_classes(binary()) -> {binary(), list(binary())}. parse_tag_and_classes(Input) -> [Tag | Classes] = gleam@string:split(Input, <<"."/utf8>>), {Tag, Classes}. -spec parse_start_tag(binary()) -> token(). parse_start_tag(Input) -> case gleam@string:split_once(Input, <<" "/utf8>>) of {ok, {Tag_and_classes, Annotation}} -> {Tag, Classes} = parse_tag_and_classes(Tag_and_classes), {start_tag, Tag, Classes, {some, Annotation}}; {error, _} -> {Tag@1, Classes@1} = parse_tag_and_classes(Input), {start_tag, Tag@1, Classes@1, none} end. -spec do_tokenize(binary(), list(token())) -> {ok, list(token())} | {error, tokenization_error()}. do_tokenize(Payload, Acc) -> case Payload of <<""/utf8>> -> {ok, Acc}; <<"> -> case gleam@string:split_once(Rest, <<">"/utf8>>) of {ok, {Tag, Rest@1}} -> do_tokenize(Rest@1, [{end_tag, Tag} | Acc]); {error, nil} -> {error, invalid_end_token} end; <<"<"/utf8, Rest@2/binary>> -> case gleam@string:split_once(Rest@2, <<">"/utf8>>) of {ok, {Tag@1, Rest@3}} -> case glubs@timestamp:parse(Tag@1, <<"."/utf8>>) of {ok, Ts} -> do_tokenize(Rest@3, [{timestamp, Ts} | Acc]); {error, _} -> do_tokenize(Rest@3, [parse_start_tag(Tag@1) | Acc]) end; {error, nil} -> {error, invalid_start_token} end; Text -> case gleam@string:split_once(Text, <<"<"/utf8>>) of {ok, {Content, Rest@4}} -> do_tokenize( <<"<"/utf8, Rest@4/binary>>, [{text, Content} | Acc] ); {error, nil} -> {ok, [{text, Text} | Acc]} end end. -spec tokenize(binary()) -> {ok, list(token())} | {error, tokenization_error()}. tokenize(Payload) -> _pipe = Payload, _pipe@1 = do_tokenize(_pipe, []), gleam@result:map(_pipe@1, fun gleam@list:reverse/1).