npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

timedtext-lint

v0.3.0

Published

A CI-friendly linter for SRT and WebVTT subtitle files.

Readme

timedtext-lint

A fast, deterministic, CI-friendly linter for SRT and WebVTT subtitle/caption files.

timedtext-lint catches timing mistakes and readability problems before subtitle files ship. It runs entirely offline and can be used as a CLI or as a TypeScript/JavaScript library.

Status: early development. Feedback, test files, and rule ideas are welcome.

What it catches

  • malformed timestamps
  • cue end before cue start
  • overlapping cues
  • zero-duration cues
  • empty cues
  • exact duplicate cues
  • too many lines in a cue
  • overlong subtitle lines
  • cues that are too short
  • excessive reading speed

Install

During local development:

npm install
npm run build
node dist/cli.js examples/broken.srt

After the package is published, the intended usage is:

npx timedtext-lint subtitles/

GitHub Action

Use the action to lint subtitle files without adding a Node setup or install step to your workflow:

name: Lint subtitles

on: [pull_request]

jobs:
  timedtext-lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: electrosparklez/[email protected]
        with:
          paths: |
            subtitles/
            captions/intro.vtt
          config: .timedtextlintrc.json

paths is required and accepts one file or directory per line. Paths are resolved from the checked-out repository, and directories are scanned recursively for .srt and .vtt files. config is optional and accepts an explicit JSON configuration path. When provided, it takes priority over automatic configuration discovery; when omitted, discovery starts from the action's current workspace.

The step succeeds when there are no lint errors, fails with exit code 1 when lint errors are found, and fails with exit code 2 for invalid input, configuration, or runtime errors. See the complete example workflow.

CLI

timedtext-lint <file-or-directory> [...more paths] [options]

--format human|json|sarif
                          Output format (default: human)
--config <path>          Load a JSON config (overrides discovery)
--no-config-discovery    Disable automatic config discovery
-h, --help               Show help

Directories are scanned recursively for .srt and .vtt files.

Example output

examples/broken.srt
     2  warning  Cue has 3 lines; maximum is 2.  max-lines
     2  warning  Reading speed is 52.5 characters/sec; maximum is 20.  maximum-reading-speed
     3  warning  Line has 66 characters; maximum is 42.  max-line-length
     8  error    Cue overlaps cue 1 by 300ms.  overlapping-cues
     8  warning  Cue lasts 200ms; minimum is 500ms.  minimum-duration
    12  error    Cue ends before it starts.  end-before-start

✖ 2 errors, 6 warnings across 1 file.

Configuration

Create .timedtextlintrc.json in your project directory:

{
  "rules": {
    "overlapping-cues": "error",
    "max-lines": ["warning", { "max": 2 }],
    "max-line-length": ["warning", { "max": 42 }],
    "minimum-duration": ["warning", { "minMs": 500 }],
    "maximum-reading-speed": ["warning", { "maxCharsPerSecond": 20 }]
  }
}

When --config is omitted, timedtext-lint searches for .timedtextlintrc.json in the current working directory and then each parent directory. The nearest matching file is used, so a nested project can override a configuration higher in the directory tree.

Run without an explicit config path to use discovery:

timedtext-lint subtitles/

An explicit path always takes priority over a discovered configuration:

timedtext-lint subtitles/ --config .timedtextlintrc.json

Disable automatic discovery to use the built-in defaults even when a configuration exists in the current directory or a parent:

timedtext-lint subtitles/ --no-config-discovery

If no configuration is found, the built-in defaults are used exactly as before. A discovered file that contains malformed JSON or invalid rule settings produces an error that identifies the file instead of being ignored.

Every rule can be set to "off", "warning", or "error". Rules with numeric options use the tuple form shown above.

JSON output

timedtext-lint subtitles/ --format json

JSON output includes per-file issues and an aggregate summary, making it suitable for CI and editor integrations.

SARIF output

Use SARIF 2.1.0 output to send subtitle diagnostics to GitHub code scanning or another SARIF-compatible tool:

timedtext-lint subtitles/ --format sarif > timedtext-lint.sarif

Each diagnostic becomes a SARIF result with its rule ID, error or warning level, message, repository-relative subtitle path, and starting line. SARIF output preserves the normal exit codes, so lint errors still exit with code 1 after the complete report is written.

The following workflow assumes timedtext-lint is installed in the project's development dependencies. It runs on push, following GitHub's documented third-party SARIF pattern, because workflows triggered by pull requests from public forks cannot receive the required security-events: write permission. It intentionally does not use pull_request_target to run untrusted contributor code with elevated permissions.

The upload step uses if: always() so a complete SARIF report is uploaded even when lint findings make the preceding step fail; the workflow still retains that failure status.

name: Subtitle code scanning

on: push

permissions:
  contents: read
  security-events: write

jobs:
  timedtext-lint-sarif:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - name: Run timedtext-lint
        run: npx timedtext-lint subtitles/ --format sarif > timedtext-lint.sarif
      - name: Upload SARIF
        if: always()
        uses: github/codeql-action/upload-sarif@v4
        with:
          sarif_file: timedtext-lint.sarif
          category: timedtext-lint

See the complete SARIF workflow example.

Exit codes

  • 0: no errors (warnings may exist)
  • 1: one or more lint errors
  • 2: CLI/configuration/runtime failure

Library API

import { lintText } from 'timedtext-lint';

const result = lintText(`1\n00:00:01,000 --> 00:00:02,000\nHello!\n`, 'example.srt');

console.log(result.issues);

Why this project?

Subtitle mistakes are easy to miss in review because timing, line length, readability, and file syntax live in the same tiny text format. A linter turns those checks into repeatable, reviewable automation.

The project provides a small core that works locally, in CI, and through a first-class GitHub Action.

Roadmap

  • [x] SRT parsing
  • [x] WebVTT parsing
  • [x] modular lint rules
  • [x] human-readable output
  • [x] JSON output
  • [x] JSON rule configuration
  • [x] automatic config discovery
  • [x] recursive directory scanning
  • [x] CI test workflow
  • [x] GitHub Action wrapper
  • [x] SARIF / GitHub code-scanning output
  • [ ] safe --fix operations
  • [ ] richer WebVTT validation
  • [ ] documentation site and browser demo

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md.

License

MIT