diff --git a/.Rbuildignore b/.Rbuildignore index ecd06c4..075363e 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -8,3 +8,5 @@ ^synced_episode.srt/ ^cran-comments\.md$ ^codemeta\.json$ +^CRAN-SUBMISSION$ +^tests.R$ diff --git a/DESCRIPTION b/DESCRIPTION index 01497b0..385287a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -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 = "francois.keck@gmail.com", role = c("aut", "cre", "cph"), @@ -13,9 +13,9 @@ Authors@R: c( comment = c(ORCID = "0000-0002-3827-1063")) ) Maintainer: Francois Keck -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 @@ -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 diff --git a/R/assert_objects.R b/R/assert_objects.R index 7bb219f..1188237 100644 --- a/R/assert_objects.R +++ b/R/assert_objects.R @@ -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\".") @@ -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) diff --git a/R/read_subtitles.R b/R/read_subtitles.R index 3d4f019..8c8d50e 100644 --- a/R/read_subtitles.R +++ b/R/read_subtitles.R @@ -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 @@ -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) @@ -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") diff --git a/R/unnest_tokens.R b/R/unnest_tokens.R index 97c96e7..ca2c505 100644 --- a/R/unnest_tokens.R +++ b/R/unnest_tokens.R @@ -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, diff --git a/R/utils_regexes.R b/R/utils_regexes.R index ca8c800..28c8dba 100644 --- a/R/utils_regexes.R +++ b/R/utils_regexes.R @@ -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) @@ -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) @@ -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 diff --git a/R/utils_time.R b/R/utils_time.R index e4eb48f..ae51373 100644 --- a/R/utils_time.R +++ b/R/utils_time.R @@ -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 = ":") diff --git a/R/write_subtitles.R b/R/write_subtitles.R index 021edb6..8bf60bb 100644 --- a/R/write_subtitles.R +++ b/R/write_subtitles.R @@ -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") { diff --git a/README.Rmd b/README.Rmd index a5215de..9cd3d12 100644 --- a/README.Rmd +++ b/README.Rmd @@ -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 @@ -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} diff --git a/README.md b/README.md index 4680e0a..4d184f0 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/codemeta.json b/codemeta.json index c325cc1..21f9ecf 100644 --- a/codemeta.json +++ b/codemeta.json @@ -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", diff --git a/cran-comments.md b/cran-comments.md index 858617d..87d032b 100644 --- a/cran-comments.md +++ b/cran-comments.md @@ -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 diff --git a/man/as_subtitle.Rd b/man/as_subtitle.Rd index 59f2afe..753760d 100644 --- a/man/as_subtitle.Rd +++ b/man/as_subtitle.Rd @@ -57,6 +57,9 @@ uses \code{\link[readr]{guess_encoding}} to detect encoding.} \item{...}{passed on to downstream methods.} } +\value{ +An object of class \code{subtitles} (see \code{\link{subtitles}}). +} \description{ Convert an R object to a \code{subtitles} object } diff --git a/man/get_subtitles_info.Rd b/man/get_subtitles_info.Rd index 17a6556..a856b3a 100644 --- a/man/get_subtitles_info.Rd +++ b/man/get_subtitles_info.Rd @@ -9,6 +9,9 @@ get_subtitles_info(x) \arguments{ \item{x}{a \code{subtitles} or \code{multisubtitles} object.} } +\value{ +Called for its side effects (printing to the console). Returns \code{NULL} invisibly. +} \description{ Get basic informations for subtitle objects } diff --git a/man/print.multisubtitles.Rd b/man/print.multisubtitles.Rd index 13d6feb..fa32631 100644 --- a/man/print.multisubtitles.Rd +++ b/man/print.multisubtitles.Rd @@ -13,6 +13,9 @@ \item{...}{further arguments passed to or from other methods.} } +\value{ +Called for its side effects (printing to the console). Returns \code{NULL} invisibly. +} \description{ Print method for multisubtitles } diff --git a/man/unnest_tokens.Rd b/man/unnest_tokens.Rd index 8ff0196..e6c2e9a 100644 --- a/man/unnest_tokens.Rd +++ b/man/unnest_tokens.Rd @@ -89,6 +89,8 @@ and \code{pattern} for "regex".} to take into account the split of the input column.} } \value{ +A tibble. + A tibble. } \description{ diff --git a/man/write_subtitles.Rd b/man/write_subtitles.Rd index 33a627e..2d9425b 100644 --- a/man/write_subtitles.Rd +++ b/man/write_subtitles.Rd @@ -16,6 +16,9 @@ Not used (only SubRip format is currently implemented).} \item{encoding}{the name of the encoding to be used.} } +\value{ +Called for its side effects (writing to \code{file}). Returns \code{NULL} invisibly. +} \description{ This function writes a \code{subtitles} object in a file. } diff --git a/tests/testthat.R b/tests/testthat.R index ed4e04f..23282b1 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -1,4 +1,3 @@ library(testthat) -library(subtools) test_check("subtools") diff --git a/tests/testthat/_snaps/unnest_tokens.md b/tests/testthat/_snaps/unnest_tokens.md index 62d5fd8..10f6611 100644 --- a/tests/testthat/_snaps/unnest_tokens.md +++ b/tests/testthat/_snaps/unnest_tokens.md @@ -4,17 +4,42 @@ unnest_tokens(tbl = s) Output # A tibble: 36 x 5 - ID Timecode_in Timecode_out Text_content test -