Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
^synced_episode.srt/
^cran-comments\.md$
^codemeta\.json$
^CRAN-SUBMISSION$
^tests.R$
10 changes: 6 additions & 4 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Package: subtools
Type: Package
Title: Read and Manipulate Video Subtitles
Version: 1.1.0
Date: 2026-03-11
Date: 2026-03-16
Authors@R: c(
person("Francois", "Keck", email = "[email protected]",
role = c("aut", "cre", "cph"),
Expand All @@ -13,9 +13,9 @@ Authors@R: c(
comment = c(ORCID = "0000-0002-3827-1063"))
)
Maintainer: Francois Keck <[email protected]>
Description: A collection of functions to read, write and manipulate video subtitles.
Supported formats include 'srt', 'subrip', 'sub', 'subviewer', 'microdvd',
'ssa', 'ass', 'substation', 'vtt', and 'webvtt'.
Description: A collection of functions to read, write and manipulate video
subtitles. Supported formats include 'srt', 'subrip', 'sub', 'subviewer',
'microdvd', 'ssa', 'ass', 'substation', 'vtt', and 'webvtt'.
Language: en-GB
License: GPL-3
LazyData: TRUE
Expand All @@ -40,3 +40,5 @@ Config/testthat/edition: 3
Config/testthat/parallel: false
Config/testthat/start-first: *assert*, *utils*, *read*
RoxygenNote: 7.3.3
X-schema.org-applicationCategory: Text Processing
X-schema.org-keywords: subtitles, video, text, srt, vtt, ssa, natural language processing
28 changes: 23 additions & 5 deletions R/assert_objects.R
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
# .assert_subtitles
# .extract_metadata

# Sanity check for subtitles objects
#' Validate a subtitles object
#'
#' Checks that \code{x} inherits from both \code{"subtitles"} and \code{"data.frame"},
#' has the four required columns (\code{ID}, \code{Timecode_in}, \code{Timecode_out},
#' \code{Text_content}), and that the two timecode columns are of class \code{"hms"}.
#' Stops with an informative error message if any check fails.
#'
#' @param x an object to validate.
#'
#' @returns Called for its side effects (validation). Returns \code{NULL} invisibly.
#'
#' @noRd
.assert_subtitles <- function(x) {
if (!is(x, "subtitles")) {
stop("A subtitles object must inherit from class \"subtitles\".")
Expand Down Expand Up @@ -35,7 +43,17 @@
}
}

#extract metadata columns from a subtitles object
#' Extract metadata columns from a subtitles object
#'
#' Returns all columns that are not part of the core subtitle structure
#' (\code{ID}, \code{Timecode_in}, \code{Timecode_out}, \code{Text_content}).
#'
#' @param x a \code{subtitles} object.
#'
#' @returns A data frame (or tibble) containing only the metadata columns of \code{x}.
#' If there are no metadata columns, an empty data frame is returned.
#'
#' @noRd
extract_metadata <- function(x) {
sub_names <- c("ID", "Timecode_in", "Timecode_out", "Text_content")
md_names <- setdiff(colnames(x), sub_names)
Expand Down
7 changes: 7 additions & 0 deletions R/read_subtitles.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
#' @param encoding the name of the encoding to be used. Default is "\code{auto}" and
#' uses \code{\link[readr]{guess_encoding}} to detect encoding.
#' @param ... passed on to downstream methods.
#'
#' @returns An object of class \code{subtitles} (see \code{\link{subtitles}}).
#'
#' @importFrom utils read.csv
#' @export
#' @examples
Expand Down Expand Up @@ -397,6 +400,8 @@ subtitles <- function(
#' @param printlen the maximum number of subtitles to print.
#' @param ... further arguments passed to or from other methods.
#'
#' @returns Called for its side effects (printing to the console). Returns \code{NULL} invisibly.
#'
#' @examples
#' f <- system.file("extdata", "ex_subrip.srt", package = "subtools")
#' s <- read_subtitles(f)
Expand All @@ -421,6 +426,8 @@ print.multisubtitles <- function(x, printlen = 10L, ...) {
#'
#' @param x a \code{subtitles} or \code{multisubtitles} object.
#'
#' @returns Called for its side effects (printing to the console). Returns \code{NULL} invisibly.
#'
#' @examples
#' s <- read_subtitles(
#' system.file("extdata", "ex_subrip.srt", package = "subtools")
Expand Down
3 changes: 3 additions & 0 deletions R/unnest_tokens.R
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ unnest_tokens.subtitles <- function(
#' The default method (\code{unnest_tokens.default}) delegates to the original implementation.
#' See "?unnest_tokens.subtitles" for the \code{subtools} specific documentation.
#' @inheritParams tidytext::unnest_tokens
#'
#' @returns A tibble.
#'
#' @export
unnest_tokens <- function(
tbl,
Expand Down
59 changes: 54 additions & 5 deletions R/utils_regexes.R
Original file line number Diff line number Diff line change
@@ -1,17 +1,45 @@
# Extract file name from dir paths
#' Extract the file name from a path
#'
#' Strips trailing slashes and returns the last path component.
#'
#' @param x a character vector of file or directory paths.
#'
#' @returns A character vector of file names, the same length as \code{x}.
#'
#' @noRd
.extr_filename <- function(x) {
x <- gsub("/+$", "", x)
x <- regmatches(x, regexpr("([^/]+$)", x))
return(x)
}

# Extract extension from file name
#' Extract the extension from a file name
#'
#' Returns the part of the file name after the last dot (lower-case alphanumeric only).
#'
#' @param x a character vector of file names or paths.
#'
#' @returns A character vector of file extensions (without the leading dot),
#' the same length as \code{x}.
#'
#' @noRd
.extr_extension <- function(x) {
x <- regmatches(x, regexpr("(?<=\\.)[0-9a-z]+$", x, perl = TRUE))
return(x)
}

# Extract Season number
#' Extract season number from file paths
#'
#' Parses the file name component of each path and attempts to detect the season
#' number using four common naming conventions:
#' \code{S01}, \code{SEASON.2}, \code{S03E05}, and \code{2X05}.
#' Returns \code{NA} for paths where no convention matches.
#'
#' @param x a character vector of file or directory paths.
#'
#' @returns A numeric vector of season numbers, the same length as \code{x}.
#'
#' @noRd
.extr_snum <- function(x) {
x <- .extr_filename(x)
x <- toupper(x)
Expand Down Expand Up @@ -49,7 +77,18 @@
return(res)
}

# Extract Episode number
#' Extract episode number from file paths
#'
#' Parses the file name component of each path and attempts to detect the episode
#' number using four common naming conventions:
#' \code{E01}, \code{EPISODE.2}, \code{S03E05}, and \code{2x05}.
#' Returns \code{NA} for paths where no convention matches.
#'
#' @param x a character vector of file or directory paths.
#'
#' @returns A numeric vector of episode numbers, the same length as \code{x}.
#'
#' @noRd
.extr_enum <- function(x) {
x <- .extr_filename(x)
x <- toupper(x)
Expand Down Expand Up @@ -88,7 +127,17 @@
}


# For WebVTT parsing: select cue blocks only (drop REGION, STYLE, NOTE)
#' Test whether a string is a WebVTT cue timing line
#'
#' Used during WebVTT parsing to identify cue blocks and filter out
#' \code{REGION}, \code{STYLE}, and \code{NOTE} blocks.
#'
#' @param x a single character string (or \code{NA}).
#'
#' @returns A single logical: \code{TRUE} if \code{x} matches the WebVTT cue timing
#' pattern, \code{FALSE} otherwise (including when \code{x} is \code{NA}).
#'
#' @noRd
.test_cuetiming <- function(x) {
if (is.na(x)) {
res <- FALSE
Expand Down
12 changes: 11 additions & 1 deletion R/utils_time.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
# Format time as HH:MM:SS.mS
#' Parse subtitle timecode strings into hms objects
#'
#' Accepts timecode strings in \code{HH:MM:SS.mS} or \code{MM:SS.mS} format
#' (comma as decimal separator is also accepted). Missing hour components are
#' padded to zero.
#'
#' @param x a character vector of timecode strings.
#'
#' @returns An \code{hms} vector (from the \pkg{hms} package), the same length as \code{x}.
#'
#' @noRd
.format_subtime <- function(x) {
x <- gsub(",", ".", x)
x <- strsplit(x, split = ":")
Expand Down
2 changes: 2 additions & 0 deletions R/write_subtitles.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#' Not used (only SubRip format is currently implemented).
#' @param encoding the name of the encoding to be used.
#'
#' @returns Called for its side effects (writing to \code{file}). Returns \code{NULL} invisibly.
#'
#' @export
#'
write_subtitles <- function(x, file, format = "srt", encoding = "UTF-8") {
Expand Down
8 changes: 2 additions & 6 deletions README.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ if (coverage >= 90) {

[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)
[![R-CMD-check](https://github.com/fkeck/subtools/actions/workflows/check-standard.yml/badge.svg)](https://github.com/fkeck/subtools/actions/workflows/check-standard.yml)
[![Code Coverage: `r coverage`%](https://img.shields.io/badge/code_coverage-`r coverage`%25-`r colour`)](https://img.shields.io/badge/code_coverage-`r coverage`%25-`r colour`)
![Code Coverage: `r coverage`%](https://img.shields.io/badge/code_coverage-`r coverage`%25-`r colour`)

### Read, write and manipulate subtitles in R

Expand All @@ -48,11 +48,7 @@ Subtools is a R package to read, write and manipulate subtitles in R. This then
### Install
You can install the package directly from CRAN with `install.packages("subtools")` or get the latest, in development, version with:
```{r, eval = FALSE}
remotes::install_github(
repo = "fkeck/subtools@dev",
build_manual = TRUE,
build_vignettes = TRUE
)
pak::pkg_install("fkeck/subtools@dev")
```

```{r}
Expand Down
52 changes: 43 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
state and is being actively
developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)
[![R-CMD-check](https://github.com/fkeck/subtools/actions/workflows/check-standard.yml/badge.svg)](https://github.com/fkeck/subtools/actions/workflows/check-standard.yml)
[![Code Coverage:
82%](https://img.shields.io/badge/code_coverage-82%25-green)](https://img.shields.io/badge/code_coverage-82%25-green)
![Code Coverage:
82%](https://img.shields.io/badge/code_coverage-82%25-green)

### Read, write and manipulate subtitles in R

Expand All @@ -30,11 +30,7 @@ You can install the package directly from CRAN with
version with:

``` r
remotes::install_github(
repo = "fkeck/subtools@dev",
build_manual = TRUE,
build_vignettes = TRUE
)
pak::pkg_install("fkeck/subtools@dev")
```

``` r
Expand Down Expand Up @@ -253,7 +249,45 @@ unnest_tokens(rushmore_sub)
#> 8 180 20'43.3603" 20'43.8761" aquarium
#> 9 180 20'43.8771" 20'44.1991" where
#> 10 180 20'44.2001" 20'44.8451" scientists
#> # ℹ 39 more rows
#> 11 180 20'44.8461" 20'45.0389" can
#> 12 180 20'45.0399" 20'45.4911" lecture
#> 13 180 20'45.4921" 20'45.6849" and
#> 14 180 20'45.6859" 20'46.2017" students
#> 15 180 20'46.2027" 20'46.3955" can
#> 16 180 20'46.3965" 20'46.8478" observe
#> 17 180 20'46.8488" 20'47.2354" marine
#> 18 180 20'47.2364" 20'47.4938" life
#> 19 180 20'47.4948" 20'47.6230" in
#> 20 180 20'47.6240" 20'47.8168" its
#> 21 180 20'47.8178" 20'48.2690" natural
#> 22 181 20'48.2700" 20'48.3393" i
#> 23 181 20'48.3403" 20'48.6908" don't
#> 24 181 20'48.6918" 20'48.9720" know
#> 25 181 20'48.9730" 20'49.2532" what
#> 26 181 20'49.2542" 20'49.3938" do
#> 27 181 20'49.3948" 20'49.6046" you
#> 28 181 20'49.6056" 20'49.9561" think
#> 29 181 20'49.9571" 20'50.3076" ernie
#> 30 181 20'50.3086" 20'50.8700" aquarium
#> 31 182 20'50.9470" 20'51.4402" what
#> 32 182 20'51.4412" 20'51.9343" kind
#> 33 182 20'51.9353" 20'52.1814" of
#> 34 182 20'52.1824" 20'52.6755" fish
#> 35 182 20'52.6765" 20'53.9109" barracudas
#> 36 182 20'53.9119" 20'55.0228" stingrays
#> 37 182 20'55.0238" 20'56.3817" hammerheads
#> 38 182 20'56.3827" 20'57.3700" piranhas
#> 39 183 20'58.0520" 20'58.6840" piranhas
#> 40 183 20'58.6850" 20'59.1588" really
#> 41 183 20'59.1598" 20'59.3962" yes
#> 42 183 20'59.3972" 20'59.6336" i'm
#> 43 183 20'59.6346" 21'00.1874" talking
#> 44 183 21'00.1884" 21'00.3457" to
#> 45 183 21'00.3467" 21'00.4248" a
#> 46 183 21'00.4258" 21'00.6622" guy
#> 47 183 21'00.6632" 21'00.8205" in
#> 48 183 21'00.8215" 21'01.2161" south
#> 49 183 21'01.2171" 21'01.7700" america

unnest_tokens(bb_sub_clean, token = "sentences")
#> # A tibble: 8 × 7
Expand Down Expand Up @@ -286,7 +320,7 @@ Note that these project used the branch 0.x of `subtools`. The API is
totally different from `subtools 1.0`.

[*You beautiful, naïve, sophisticated newborn
series*](https://www.masalmon.eu/2017/11/05/newborn-serie/) by
series*](https://masalmon.eu/2017/11/05/newborn-serie/) by
[ma_salmon](https://x.com/ma_salmon)

[*A tidy text analysis of Rick and
Expand Down
4 changes: 3 additions & 1 deletion codemeta.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@
},
"SystemRequirements": null
},
"fileSize": "169.162KB",
"applicationCategory": "TextProcessing",
"keywords": ["subtitles", "video", "text", "srt", "vtt", "ssa", "naturallanguageprocessing"],
"fileSize": "186.925KB",
"codeRepository": "https://github.com/fkeck/subtools",
"releaseNotes": "https://github.com/fkeck/subtools/blob/master/NEWS.md",
"readme": "https://github.com/fkeck/subtools/blob/main/README.md",
Expand Down
6 changes: 6 additions & 0 deletions cran-comments.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@
0 errors | 0 warnings | 1 note

* This is a new release.

All functions now have a @returns value.

Thank you for taking the time to review our package.

François et Alban
3 changes: 3 additions & 0 deletions man/as_subtitle.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions man/get_subtitles_info.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions man/print.multisubtitles.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions man/unnest_tokens.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions man/write_subtitles.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading