-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
eaa4a24
commit c644cf4
Showing
7 changed files
with
226 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import gleam/string | ||
import gleam/list | ||
import gleam/result | ||
import gleam/int | ||
import gleam/string_builder.{StringBuilder} | ||
import glubs/timestamp | ||
|
||
pub type Srt { | ||
Srt(cues: List(Cue)) | ||
} | ||
|
||
// Cue represents a single cue in a srt file. | ||
pub type Cue { | ||
Cue(id: Int, start_time: Int, end_time: Int, payload: String) | ||
} | ||
|
||
// Parses a Srt string and returns a Result containing the parsed Srt structure or a parsing error. | ||
pub fn parse(input: String) -> Result(Srt, String) { | ||
input | ||
|> string.replace("\r\n", "\n") | ||
|> string.trim_right() | ||
|> string.split("\n\n") | ||
|> list.try_map(parse_cue) | ||
|> result.map(Srt(cues: _)) | ||
} | ||
|
||
/// Converts a Srt type to a string. | ||
pub fn to_string(srt: Srt) -> String { | ||
srt.cues | ||
|> list.map(cue_to_string) | ||
|> string_builder.join("\n\n") | ||
|> string_builder.append("\n") | ||
|> string_builder.to_string() | ||
} | ||
|
||
fn cue_to_string(cue: Cue) -> StringBuilder { | ||
let start_time = timestamp.to_string(cue.start_time, ",") | ||
let end_time = timestamp.to_string(cue.end_time, ",") | ||
|
||
[ | ||
string_builder.from_string(int.to_string(cue.id)), | ||
start_time | ||
|> string_builder.append(" --> ") | ||
|> string_builder.append_builder(end_time), | ||
string_builder.from_string(cue.payload), | ||
] | ||
|> string_builder.join("\n") | ||
} | ||
|
||
fn parse_cue(input: String) -> Result(Cue, String) { | ||
let [id, ts, ..lines] = string.split(input, "\n") | ||
|
||
use id <- result.try( | ||
id | ||
|> int.parse() | ||
|> result.replace_error("Cannot parse identifier"), | ||
) | ||
|
||
use #(start_time, end_time) <- result.try(timestamp.parse_range(ts, ",")) | ||
|
||
Ok(Cue( | ||
id: id, | ||
start_time: start_time, | ||
end_time: end_time, | ||
payload: string.join(lines, "\n"), | ||
)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import gleam/result | ||
import gleam/string | ||
import gleam/string_builder.{StringBuilder} | ||
import gleam/int | ||
|
||
// Parses the given string to a timestamp. | ||
pub fn parse(input: String, fraction_sep: String) -> Result(Int, Nil) { | ||
use #(h, m, s_ms) <- result.try({ | ||
case string.split(input, on: ":") { | ||
[m, s_ms] -> Ok(#("0", m, s_ms)) | ||
[h, m, s_ms] -> Ok(#(h, m, s_ms)) | ||
_ -> Error(Nil) | ||
} | ||
}) | ||
|
||
use h <- result.try(int.parse(h)) | ||
use m <- result.try(int.parse(m)) | ||
use #(s, ms) <- result.try(split_seconds(s_ms, fraction_sep)) | ||
|
||
Ok({ s + m * 60 + h * 60 * 60 } * 1000 + ms) | ||
} | ||
|
||
// Parses a timestamp range. | ||
pub fn parse_range( | ||
line: String, | ||
fraction_sep: String, | ||
) -> Result(#(Int, Int), String) { | ||
case string.split(line, " --> ") { | ||
[start, end] -> { | ||
use start <- result.try( | ||
start | ||
|> parse(fraction_sep) | ||
|> result.replace_error("Invalid start timestamp"), | ||
) | ||
|
||
use end <- result.try( | ||
end | ||
|> parse(fraction_sep) | ||
|> result.replace_error("Invalid end timestamp"), | ||
) | ||
|
||
Ok(#(start, end)) | ||
} | ||
_other -> Error("Invalid timestamp") | ||
} | ||
} | ||
|
||
// Converts the given ms to a timestamp. | ||
pub fn to_string(ms: Int, fraction_sep: String) -> StringBuilder { | ||
let hours = pad({ ms / 3_600_000 }, 2) | ||
let minutes = pad({ { ms % 3_600_000 } / 60_000 }, 2) | ||
let seconds = pad({ ms % 60_000 } / 1000, 2) | ||
let ms = pad(ms % 1000, 3) | ||
|
||
string_builder.from_strings([ | ||
hours, | ||
":", | ||
minutes, | ||
":", | ||
seconds, | ||
fraction_sep, | ||
ms, | ||
]) | ||
} | ||
|
||
fn split_seconds( | ||
input: String, | ||
fraction_sep: String, | ||
) -> Result(#(Int, Int), Nil) { | ||
case string.split(input, on: fraction_sep) { | ||
[_s] -> { | ||
use s <- result.try(int.parse(input)) | ||
Ok(#(s, 0)) | ||
} | ||
[s, ms] -> { | ||
use s <- result.try(int.parse(s)) | ||
use ms <- result.try(int.parse(ms)) | ||
Ok(#(s, ms)) | ||
} | ||
_other -> Error(Nil) | ||
} | ||
} | ||
|
||
fn pad(number: Int, count: Int) -> String { | ||
number | ||
|> int.to_string() | ||
|> string.pad_left(count, "0") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
1 | ||
00:02:16,612 --> 00:02:19,376 | ||
Senator, we're making | ||
our final approach into Coruscant. | ||
|
||
2 | ||
00:02:19,482 --> 00:02:21,609 | ||
Very good, Lieutenant. | ||
|
||
3 | ||
00:03:13,336 --> 00:03:15,167 | ||
We made it. | ||
|
||
4 | ||
00:03:18,608 --> 00:03:20,371 | ||
I guess I was wrong. | ||
|
||
5 | ||
00:03:20,476 --> 00:03:22,671 | ||
There was no danger at all. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import simplifile | ||
import glubs/srt.{Cue, Srt} | ||
import gleeunit/should | ||
|
||
pub fn parse_example_test() { | ||
let assert Ok(content) = simplifile.read("test/fixtures/example.srt") | ||
|
||
content | ||
|> srt.parse() | ||
|> should.equal(Ok(example())) | ||
} | ||
|
||
pub fn to_string_example_test() { | ||
let assert Ok(expected) = simplifile.read("test/fixtures/example.srt") | ||
|
||
example() | ||
|> srt.to_string() | ||
|> should.equal(expected) | ||
} | ||
|
||
fn example() -> Srt { | ||
Srt([ | ||
Cue( | ||
1, | ||
136_612, | ||
139_376, | ||
"Senator, we're making\nour final approach into Coruscant.", | ||
), | ||
Cue(2, 139_482, 141_609, "Very good, Lieutenant."), | ||
Cue(3, 193_336, 195_167, "We made it."), | ||
Cue(4, 198_608, 200_371, "I guess I was wrong."), | ||
Cue(5, 200_476, 202_671, "There was no danger at all."), | ||
]) | ||
} |