Skip to content

Commit

Permalink
Merge pull request #7 from grain-team/ryan/support-voice-spans
Browse files Browse the repository at this point in the history
  • Loading branch information
bismark authored Mar 22, 2023
2 parents ad61a82 + 5270840 commit 599df47
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 36 deletions.
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,33 @@ iex> vtt = """
Hello world!
"""
...> Vttyl.parse(vtt) |> Enum.into([])
[%Vttyl.Part{end: 17609, part: 1, start: 15450, text: "Hello world!"}]
[%Vttyl.Part{end: 17609, part: 1, start: 15450, text: "Hello world!", voice: nil}]
```

#### Stream Parsing

```elixir
iex> "same_text.vtt" |> File.stream!([], 2048) |> Vttyl.parse_stream() |> Enum.into([])
[%Vttyl.Part{end: 17609, part: 1, start: 15450, text: "Hello world!"}]
[%Vttyl.Part{end: 17609, part: 1, start: 15450, text: "Hello world!", voice: nil}]
```

#### Simple Voice Spans

(Closing voice spans are currently not supported)

```elixir
iex> vtt = """
WEBVTT
1
00:00:15.450 --> 00:00:17.609
<v Andy>Hello world!
"""
...> Vttyl.parse(vtt) |> Enum.into([])
[%Vttyl.Part{end: 17609, part: 1, start: 15450, text: "Hello world!", voice: "Andy"}]
```


### Encoding

Vttyl also supports encoding parts.
Expand All @@ -60,6 +77,17 @@ Hello world!
"""
```

```elixir
iex> parts = [%Vttyl.Part{end: 17609, part: 1, start: 15450, text: "Hello world!", voice: "Andy"}]
...> Vttyle.encode(parts)
"""
WEBVTT
1
00:00:15.450 --> 00:00:17.609
<v Andy>Hello world!
"""
```

## License

Vttyl is Copyright © 2019 Grain Intelligence, Inc. It is free software, and may be
Expand Down
31 changes: 1 addition & 30 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,30 +1 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# third-party users, it should be done in your "mix.exs" file.

# You can configure your application as:
#
# config :vttyl, key: :value
#
# and access this configuration in your application as:
#
# Application.get_env(:vttyl, :key)
#
# You can also configure a third-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env()}.exs"
import Config
12 changes: 11 additions & 1 deletion lib/vttyl/decode.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ defmodule Vttyl.Decode do

# Text content should be on one line and the other stuff should have appeared
not is_nil(acc.part) and not is_nil(acc.start) and not is_nil(acc.end) and line != "" ->
%Part{acc | text: line}
{voice, text} = parse_text(line)
%Part{acc | text: text, voice: voice}

true ->
acc
Expand Down Expand Up @@ -59,6 +60,15 @@ defmodule Vttyl.Decode do
Regex.match?(@line_regex, line)
end

@annotation_space_regex ~r/[ \t]/
defp parse_text("<v" <> line) do
[voice, text] = String.split(line, ">", parts: 2)
[_, voice] = String.split(voice, @annotation_space_regex, parts: 2)
{voice, text}
end

defp parse_text(text), do: {nil, text}

defp parse_timestamps(line) do
line
|> String.split("-->")
Expand Down
11 changes: 10 additions & 1 deletion lib/vttyl/encode.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
defmodule Vttyl.Encode do
@moduledoc false
alias Vttyl.Part

@spec encode_part(Part.t(), :vtt | :srt) :: String.t()
def encode_part(part, type) do
ts = fmt_timestamp(part.start, type) <> " --> " <> fmt_timestamp(part.end, type)
Enum.join([part.part, ts, part.text], "\n")

text =
if type == :vtt && part.voice do
"<v #{part.voice}>" <> part.text
else
part.text
end

Enum.join([part.part, ts, text], "\n")
end

@hour_ms 3_600_000
Expand Down
5 changes: 3 additions & 2 deletions lib/vttyl/part.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ defmodule Vttyl.Part do
part: non_neg_integer(),
start: millisecond :: integer,
end: millisecond :: integer,
text: String.t()
text: String.t(),
voice: String.t() | nil
}
defstruct [:start, :end, :text, :part]
defstruct [:start, :end, :text, :part, :voice]
end
17 changes: 17 additions & 0 deletions priv/samples/voice.vtt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
WEBVTT
1
00:00.000 --> 00:02.000
<v.first.loud Esme>It’s a blue apple tree!

2
00:02.000 --> 00:04.000
<v Mary>No way!

3
00:04.000 --> 00:06.000
<v Esme F>Hee!

4
00:06.000 --> 00:08.000
<v.loud Mary>That’s awesome!
12 changes: 12 additions & 0 deletions test/vttyl_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ defmodule VttylTest do

assert length(parsed) == 20
end

test "voice spans" do
parsed =
"voice.vtt"
|> get_vtt_file()
|> File.stream!([], 2048)
|> Vttyl.parse_stream()
|> Stream.map(& &1.voice)
|> Enum.into([])

assert ["Esme", "Mary", "Esme F", "Mary"] == parsed
end
end

describe "encode_vtt/1" do
Expand Down

0 comments on commit 599df47

Please sign in to comment.