independent-subtitle-parser 0.0.0-stage → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -0
- package/CONTRIBUTING.md +81 -0
- package/LICENSE +21 -0
- package/README.md +194 -2
- package/THIRD_PARTY_NOTICES.md +60 -0
- package/dist/context.d.ts +22 -0
- package/dist/context.js +70 -0
- package/dist/decode.d.ts +3 -0
- package/dist/decode.js +51 -0
- package/dist/detect.d.ts +3 -0
- package/dist/detect.js +38 -0
- package/dist/entities.d.ts +1 -0
- package/dist/entities.js +2235 -0
- package/dist/formats/ass.d.ts +3 -0
- package/dist/formats/ass.js +298 -0
- package/dist/formats/sami.d.ts +3 -0
- package/dist/formats/sami.js +594 -0
- package/dist/formats/timed.d.ts +2 -0
- package/dist/formats/timed.js +149 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +67 -0
- package/dist/text.d.ts +9 -0
- package/dist/text.js +109 -0
- package/dist/time.d.ts +5 -0
- package/dist/time.js +23 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.js +1 -0
- package/docs/API.md +159 -0
- package/docs/CI.md +55 -0
- package/docs/CORPUS-016.md +112 -0
- package/docs/CORPUS-017.md +148 -0
- package/docs/CORPUS.md +163 -0
- package/docs/DESIGN.md +94 -0
- package/docs/DEVELOPMENT.md +104 -0
- package/docs/DIAGNOSTICS.md +65 -0
- package/docs/IMPROVEMENTS.md +91 -0
- package/docs/QUALITY.md +53 -0
- package/docs/RELEASE.md +109 -0
- package/docs/REPORTING.md +73 -0
- package/docs/SOURCES.md +70 -0
- package/docs/USAGE.md +118 -0
- package/examples/browser.html +36 -0
- package/examples/browser.mjs +107 -0
- package/examples/node.mjs +41 -0
- package/examples/parse-worker.mjs +12 -0
- package/examples/serve.mjs +24 -0
- package/examples/smoke.html +37 -0
- package/examples/worker-client.mjs +28 -0
- package/examples/workflow.mjs +35 -0
- package/package.json +48 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.7 — legacy SAMI commands and large real ASS/SSA validation, unpublished
|
|
4
|
+
|
|
5
|
+
- Connect the public GitHub source repository, issue/PR routes and npm repository
|
|
6
|
+
metadata. Prepare the public 0.1.7 manifest and registry/tarball installation
|
|
7
|
+
instructions; actual npm publication remains pending separate approval.
|
|
8
|
+
- Prepare public-facing README, contribution/reporting guides and GitHub issue/PR
|
|
9
|
+
templates. Include contribution/reporting guides in the package. Add a separate
|
|
10
|
+
release package check for the public manifest.
|
|
11
|
+
- Retain observed `SpanID`, `ShowImg` and `DisplayImg` authoring commands as opaque
|
|
12
|
+
`sami-legacy-command` metadata; exclude complete commands from reading text so
|
|
13
|
+
they cannot create phantom captions or prevent NBSP clears. Warn once with
|
|
14
|
+
`SAMI_LEGACY_COMMAND_UNAPPLIED`. No command effects are executed.
|
|
15
|
+
- Recover incomplete commands at the next `<`; preserve their literal text and
|
|
16
|
+
diagnose malformed syntax. Quoted `>` and unquoted apostrophes are covered.
|
|
17
|
+
- Add seven hash-pinned local evaluation files, including 7.6 MB and 5.9 MB ASS
|
|
18
|
+
animation-heavy originals and three long-form SSA v4 files. Independent
|
|
19
|
+
expectations cover every event's time/raw fields and a declared reading subset.
|
|
20
|
+
- Score three previously unscored legacy SAMI files under a narrow nonspoken
|
|
21
|
+
command profile. Demo layout and interactive playback remain unscored.
|
|
22
|
+
- See [scope, comparisons and reproduction](docs/CORPUS-017.md). No npm release
|
|
23
|
+
or product integration has been performed.
|
|
24
|
+
|
|
25
|
+
## 0.1.6 — ASS/SSA/SAMI corpus validation, unpublished
|
|
26
|
+
|
|
27
|
+
- Recover unpadded ASS minute/second fields with `ASS_TIME_WIDTH_RECOVERED`;
|
|
28
|
+
keep centisecond precision strict and preserve original event fields.
|
|
29
|
+
- Read legacy SAMI `SAMI_Type` as well as `SAMIType` into track kind.
|
|
30
|
+
- Warn once per document for unapplied SAMI Source ID speaker persistence;
|
|
31
|
+
retain its paragraph text and attributes without claiming layout support.
|
|
32
|
+
- Add 18 pinned, licensed upstream ASS/SSA/SAMI originals and independent
|
|
33
|
+
source-derived oracles. Evaluate 19 additional local-only originals without
|
|
34
|
+
redistributing transcripts of uncertain license. Record unscored legacy
|
|
35
|
+
player commands separately from passing cases.
|
|
36
|
+
- Expand the mutation corpus and current-runtime correctness, package and
|
|
37
|
+
performance checks. Keep `private: true`; no adapters or publication.
|
|
38
|
+
|
|
39
|
+
## 0.1.5 — publication preparation, unpublished
|
|
40
|
+
|
|
41
|
+
- Confirm the package name and apply the owner-selected MIT license to original
|
|
42
|
+
code. Keep `private: true` until a separately authorized publication.
|
|
43
|
+
- Check actual archive bytes against source and installed files, verify npm
|
|
44
|
+
integrity, enforce the package allowlist and require repeatable tarball bytes.
|
|
45
|
+
- Bound browser track options to 100 per page and shorten displayed labels;
|
|
46
|
+
all tracks remain selectable and complete results remain in JSON.
|
|
47
|
+
- Add pinned, licensed libass and Microsoft WMP samples with independent
|
|
48
|
+
expectations, plus a reproducible external-writer integration comparison.
|
|
49
|
+
- Expand fuzz seeds and clarify the remaining corpus and platform gaps.
|
|
50
|
+
- Parser runtime and public API remain unchanged from 0.1.4. No adapters,
|
|
51
|
+
product changes or npm publication.
|
|
52
|
+
|
|
53
|
+
## 0.1.4 — consumer usability iteration, unpublished
|
|
54
|
+
|
|
55
|
+
- Reorganize README around installed-package use; move detailed contract to API
|
|
56
|
+
reference and add diagnostic, usage and development guides.
|
|
57
|
+
- Include runnable Node and browser file examples in the tarball. Browser parsing
|
|
58
|
+
runs in a disposable Worker with cancel/deadline/stale-result handling, explicit
|
|
59
|
+
encoding/format choices, track selection, labeled previews and complete JSON.
|
|
60
|
+
- Keep decode and parse diagnostics distinct, unknown cue ends unchanged, and
|
|
61
|
+
text output inert. No file upload, storage, charset guessing or rendering.
|
|
62
|
+
- Retain the existing public API and TypeScript interfaces; workflow helpers are
|
|
63
|
+
consumer recipes, not package exports. Parser source is unchanged from 0.1.3.
|
|
64
|
+
- Correct stale documentation about the already completed product comparison.
|
|
65
|
+
- Preserve historical quality evidence; no product adapters or npm publication.
|
|
66
|
+
|
|
67
|
+
## 0.1.3 — corpus and robustness iteration, unpublished
|
|
68
|
+
|
|
69
|
+
- Fix WebVTT missing-separator recovery assigning header metadata as a cue ID.
|
|
70
|
+
- Recover SRT single-dash arrows and one terminal period after a complete comma
|
|
71
|
+
end timestamp, with dedicated warnings and exact source retention. These occur
|
|
72
|
+
in Blender's published Portuguese and Indonesian subtitles.
|
|
73
|
+
- Diagnose preserved unterminated WebVTT markup instead of returning clean status.
|
|
74
|
+
- Correct windows-1252 C1 characters on affected Node/ICU decoders using the
|
|
75
|
+
WHATWG mapping; aliases resolve through TextDecoder. Other encodings remain
|
|
76
|
+
runtime-dependent. No charset detection was introduced.
|
|
77
|
+
- Avoid the SAMI markup scanner for plain paragraphs and gaps while preserving
|
|
78
|
+
HTML ASCII whitespace and non-ASCII spaces.
|
|
79
|
+
- Add pinned WPT and licensed Blender corpus, six OSS comparison targets,
|
|
80
|
+
isolated performance/memory harnesses and reproducible sustained fuzzing.
|
|
81
|
+
- Test Node 20 and 24 plus Chromium. Evidence and remaining limits are recorded
|
|
82
|
+
in the local `evidence/quality-013-final/REPORT.md` and `docs/CORPUS.md`.
|
|
83
|
+
- No adapters, product changes, runtime dependencies or publication.
|
|
84
|
+
|
|
85
|
+
## 0.1.2 — local quality iteration, unpublished
|
|
86
|
+
|
|
87
|
+
- Fix valid WebVTT class names containing Unicode spaces and voice annotations
|
|
88
|
+
containing a literal `<`. Preserve the original tag in raw text and fields.
|
|
89
|
+
- Add 19 focused text regressions and generated arithmetic, newline, Unicode
|
|
90
|
+
decode and malformed-content invariant checks.
|
|
91
|
+
- Define evidence-based public npm release gates and defer product adapters.
|
|
92
|
+
- No public API shape changes. Existing 0.1.0/0.1.1 and product-comparison evidence
|
|
93
|
+
remains unchanged. Full WebVTT semantic trees and ASS rendering remain absent.
|
|
94
|
+
|
|
95
|
+
## 0.1.1 — unpublished
|
|
96
|
+
|
|
97
|
+
See `docs/IMPROVEMENTS.md` and the frozen `evidence/improvements-1/REPORT.md` in
|
|
98
|
+
the local source workspace for the original improvement record.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Contributions can improve parsing, independent test expectations, documentation
|
|
4
|
+
or consumer examples. Keep changes focused on the documented extraction profile.
|
|
5
|
+
Please explain the input that fails and the behavior your change establishes.
|
|
6
|
+
|
|
7
|
+
The canonical repository is
|
|
8
|
+
[saramkim/independent-subtitle-parser](https://github.com/saramkim/independent-subtitle-parser).
|
|
9
|
+
Use [Issues](https://github.com/saramkim/independent-subtitle-parser/issues) for
|
|
10
|
+
reports and proposals. Fork the repository, make a focused branch, then open a
|
|
11
|
+
[pull request](https://github.com/saramkim/independent-subtitle-parser/pulls)
|
|
12
|
+
against `main`. See [Reporting bugs](docs/REPORTING.md). No response time is promised.
|
|
13
|
+
|
|
14
|
+
## Set up a source checkout
|
|
15
|
+
|
|
16
|
+
Use Node.js 20+ and npm. Package validation also uses `tar` from PATH.
|
|
17
|
+
Runtime dependencies remain empty; TypeScript is a development dependency.
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npm ci --ignore-scripts
|
|
21
|
+
npm run build
|
|
22
|
+
npm test
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For a focused change, build and run the relevant test file first:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm run build
|
|
29
|
+
node --test test/sami.test.mjs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Before proposing a finished patch, run `npm run validate`. It builds the package,
|
|
33
|
+
runs the suite, installs/checks the real tarball, runs the Node example and records
|
|
34
|
+
a benchmark. Evidence is written to a fresh ignored directory. See the
|
|
35
|
+
[development guide](docs/DEVELOPMENT.md) for custom output paths and PowerShell.
|
|
36
|
+
Do not label an unexecuted platform/browser check as passed. For documentation
|
|
37
|
+
changes, validate changed examples and package links; unrelated long fuzz
|
|
38
|
+
campaigns need not be rerun when their runtime inputs are unchanged.
|
|
39
|
+
|
|
40
|
+
## Establish the expected result independently
|
|
41
|
+
|
|
42
|
+
- Cite a public format specification or a clearly described compatibility policy.
|
|
43
|
+
For real files, record provenance, encoding, revision and SHA-256.
|
|
44
|
+
- Add the smallest useful reproduction. Preserve the original failing case and
|
|
45
|
+
show that the proposed fix changes it for the stated reason.
|
|
46
|
+
- Do not change an expected value merely because this parser or another library
|
|
47
|
+
returns something else. Explain disagreements and distinguish parsing from
|
|
48
|
+
rendering, raw preservation from text projection, and recovery from conformance.
|
|
49
|
+
- Test important effects on times, line breaks, raw/source text, tracks, fields,
|
|
50
|
+
diagnostics and resource limits. Include malformed variants when relevant.
|
|
51
|
+
|
|
52
|
+
Tests are in `test/`; runtime code is in `src/`; examples are host applications
|
|
53
|
+
in `examples/`. The public API is the package root. Keep the core independent of
|
|
54
|
+
UI, DOM, filesystem, storage and extension APIs. Full ASS rendering, streaming
|
|
55
|
+
and serialization are outside the current scope. Discuss substantial API or
|
|
56
|
+
dependency changes with maintainers before investing in a large patch.
|
|
57
|
+
|
|
58
|
+
## Fixtures, privacy and licensing
|
|
59
|
+
|
|
60
|
+
Prefer a small synthetic subtitle that you wrote. Do not commit private captions,
|
|
61
|
+
full copyrighted transcripts, credentials, local account paths or personal data.
|
|
62
|
+
An open-source repository's code license does not necessarily license subtitles,
|
|
63
|
+
fonts or images stored beside its code. Third-party fixtures need a verified
|
|
64
|
+
redistribution basis, attribution and provenance; retain their original licenses.
|
|
65
|
+
If rights are unclear, use a synthetic reproduction or record public provenance
|
|
66
|
+
for optional local evaluation. See [corpus methodology](docs/CORPUS.md).
|
|
67
|
+
|
|
68
|
+
Preserve fixture bytes, BOM and line endings. `.gitattributes` intentionally
|
|
69
|
+
disables automatic newline conversion. Do not bulk-format corpus files.
|
|
70
|
+
Keep generated `dist/`, tarballs, dependencies, caches and local `evidence/` out
|
|
71
|
+
of commits. Original code contributions are provided under the project's
|
|
72
|
+
[MIT license](LICENSE); retain applicable third-party notices.
|
|
73
|
+
|
|
74
|
+
## Submit a focused change
|
|
75
|
+
|
|
76
|
+
Describe the problem, independent expected behavior, final change and checks
|
|
77
|
+
actually run. Mention API/diagnostic changes and known limitations. Update the
|
|
78
|
+
changelog and user-facing documentation when behavior changes. Use the prepared
|
|
79
|
+
PR template when opening a pull request. Publishing a package or
|
|
80
|
+
changing release credentials is a separate maintainer action, not a contribution
|
|
81
|
+
requirement. Be respectful and keep disagreements tied to reproducible evidence.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 independent-subtitle-parser contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,195 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Independent Subtitle Parser
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/saramkim/independent-subtitle-parser/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
Parse **SRT, WebVTT, SAMI/SMI and ASS/SSA** with one TypeScript API in browsers
|
|
6
|
+
and Node.js. Extract timed text and language tracks while keeping the source,
|
|
7
|
+
raw markup and diagnostics available for inspection.
|
|
8
|
+
|
|
9
|
+
- **One data model:** millisecond timestamps, readable text, tracks and metadata.
|
|
10
|
+
- **Explicit recovery:** malformed input, omitted data and unsupported presentation
|
|
11
|
+
produce diagnostics instead of silently disappearing.
|
|
12
|
+
- **Portable core:** no runtime dependencies, DOM, storage, network access or renderer.
|
|
13
|
+
|
|
14
|
+
Use it for subtitle import, inspection, search and text-based playback workflows.
|
|
15
|
+
For styled ASS playback, use a dedicated renderer alongside the preserved source.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Registry installation (requires a published version):
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
npm install independent-subtitle-parser
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
For an unpublished release candidate, install the supplied tarball instead:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm install ./independent-subtitle-parser-0.1.7.tgz
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
To build that tarball from a source checkout:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
git clone https://github.com/saramkim/independent-subtitle-parser.git
|
|
35
|
+
cd independent-subtitle-parser
|
|
36
|
+
npm ci --ignore-scripts
|
|
37
|
+
npm pack
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Node.js **20+**; ESM with TypeScript declarations. CommonJS callers use dynamic
|
|
41
|
+
`import()`; synchronous `require()` is not a supported export. Browser consumers
|
|
42
|
+
need ES2022, and `TextDecoder` when decoding bytes. Import the package root through
|
|
43
|
+
your application's module/bundler setup; the examples also run without a bundler.
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { parse } from 'independent-subtitle-parser';
|
|
49
|
+
|
|
50
|
+
const source = '1\n00:00:01,250 --> 00:00:03,000\n안녕하세요\nHello';
|
|
51
|
+
const result = parse(source);
|
|
52
|
+
|
|
53
|
+
if (result.status === 'fatal') {
|
|
54
|
+
console.error(result.diagnostics); // no cues, tracks or metadata returned
|
|
55
|
+
} else {
|
|
56
|
+
console.log(result.cues[0]?.start); // 1250 (milliseconds)
|
|
57
|
+
console.log(result.cues[0]?.end); // 3000
|
|
58
|
+
console.log(result.cues[0]?.text); // 안녕하세요\nHello
|
|
59
|
+
console.log(result.diagnostics); // inspect partial results too
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`parse` detects the format from content. If the format is already known, pass
|
|
64
|
+
`parse(source, { format: 'sami' })`; `.smi` files use the `sami` option.
|
|
65
|
+
`detectFormat(source)` exposes the detection result without parsing cues.
|
|
66
|
+
|
|
67
|
+
## Supported formats
|
|
68
|
+
|
|
69
|
+
| Format | Extracted and preserved | Important limits |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| SRT | Times, IDs, multiline text and raw markup; diagnosed recovery of common malformed syntax | No universal SRT standard or styling renderer; ambiguous damage can remain unrecovered |
|
|
72
|
+
| WebVTT | Times, IDs, cue settings, text/voice/language markup and NOTE/STYLE/REGION metadata | No browser layout/CSS engine; HLS timestamp mapping is retained but not applied |
|
|
73
|
+
| SAMI/SMI | SYNC times, class/language tracks, clear events, BR/PRE and legacy authoring-command metadata | Final end can be unknown; no full HTML/CSS layout, Source panel or command execution |
|
|
74
|
+
| ASS/SSA | Dialogue, centisecond times, event fields, styles/comments, raw overrides and a reading-text projection | No fonts, positioning, drawings or karaoke rendering; declared Timer scaling is not applied |
|
|
75
|
+
|
|
76
|
+
See the [complete support profile](docs/API.md) for recovery rules and unsupported
|
|
77
|
+
syntax. Source order, overlaps and duplicates are retained. The library does not
|
|
78
|
+
sort cues, infer languages, merge tracks or choose a playback track for you.
|
|
79
|
+
|
|
80
|
+
## Work with results
|
|
81
|
+
|
|
82
|
+
| Value | Meaning |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `status: 'ok'` | No warning/error in the supported extraction profile; not a full conformance certificate |
|
|
85
|
+
| `status: 'partial'` | Usable data may coexist with recovery, omitted data or unsupported presentation; inspect diagnostics |
|
|
86
|
+
| `status: 'fatal'` | No cues/tracks/metadata; diagnostics and source remain available |
|
|
87
|
+
| `end: null` | No reliable end is known; your application must choose an explicit playback policy |
|
|
88
|
+
|
|
89
|
+
`text` is a reading projection. `rawText` retains cue markup with LF-normalized
|
|
90
|
+
line endings. `sourceText` is the exact string passed to `parse`. Keep original
|
|
91
|
+
bytes separately for byte-perfect archival. Render output as text, never raw
|
|
92
|
+
`innerHTML`. A warning about unapplied styling does not necessarily mean that a
|
|
93
|
+
caption was lost. The [diagnostic guide](docs/DIAGNOSTICS.md) explains decisions.
|
|
94
|
+
|
|
95
|
+
For multilingual subtitles, inspect `result.tracks`, let the caller choose a
|
|
96
|
+
track ID, then filter `result.cues` by `cue.trackId`.
|
|
97
|
+
[Track-selection recipes](docs/USAGE.md) cover missing language metadata and
|
|
98
|
+
unknown end times.
|
|
99
|
+
|
|
100
|
+
## Decode file bytes
|
|
101
|
+
|
|
102
|
+
Decoding is separate from parsing. `decode` defaults to UTF-8 or a UTF-8/UTF-16
|
|
103
|
+
BOM; it does **not** guess legacy encodings. Supply a known encoding such as
|
|
104
|
+
`euc-kr`. Invalid byte sequences fail by default.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { decode, parse } from 'independent-subtitle-parser';
|
|
108
|
+
|
|
109
|
+
function readSubtitle(bytes: Uint8Array, encoding?: string) {
|
|
110
|
+
const decoded = decode(bytes, { encoding });
|
|
111
|
+
const parsed = decoded.text === null ? null : parse(decoded.text);
|
|
112
|
+
return { decoded, parsed }; // retain diagnostics from both stages
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Browser: readSubtitle(new Uint8Array(await file.arrayBuffer()))
|
|
116
|
+
// Node: readSubtitle(await readFile(path)) from node:fs/promises
|
|
117
|
+
// Legacy encoding: readSubtitle(bytes, 'euc-kr')
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Check file size **before** reading it. Default decoding accepts up to 16 MiB;
|
|
121
|
+
parsing has separate string, line, cue and diagnostic budgets. Replacement
|
|
122
|
+
decoding (`fatal: false`) is an explicit lossy choice: retain its warning even
|
|
123
|
+
when parsing succeeds. Invalid API options throw `TypeError` or `RangeError`;
|
|
124
|
+
file-content problems return diagnostics. Catch host I/O errors separately.
|
|
125
|
+
|
|
126
|
+
The API is synchronous and reads a whole document. Large results and JSON export
|
|
127
|
+
consume memory. For untrusted files or responsive UI, use lower
|
|
128
|
+
[resource budgets](docs/API.md) and a host-managed Worker/process with a deadline.
|
|
129
|
+
|
|
130
|
+
## Try a real file
|
|
131
|
+
|
|
132
|
+
The package includes runnable examples. From an application with the package
|
|
133
|
+
installed:
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
node node_modules/independent-subtitle-parser/examples/node.mjs ./movie.srt
|
|
137
|
+
node node_modules/independent-subtitle-parser/examples/node.mjs ./movie.smi euc-kr sami
|
|
138
|
+
node node_modules/independent-subtitle-parser/examples/serve.mjs
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The Node example writes the complete result as JSON. The server prints a local
|
|
142
|
+
URL for the browser example, which provides file/encoding/format selection,
|
|
143
|
+
track previews, diagnostics, JSON download and a cancellable module Worker.
|
|
144
|
+
File contents are not uploaded or stored. Preview limits do not truncate the
|
|
145
|
+
retained parse result. Serve over HTTP; `file:` does not reliably load modules.
|
|
146
|
+
|
|
147
|
+
In a source checkout, run `npm run build`, then `node examples/node.mjs` or
|
|
148
|
+
`node examples/serve.mjs`. See [consumer recipes](docs/USAGE.md) for CommonJS,
|
|
149
|
+
bundlers, Worker lifetime and CLI exit codes.
|
|
150
|
+
|
|
151
|
+
## Validation and compatibility
|
|
152
|
+
|
|
153
|
+
[Hosted CI](docs/CI.md) passed **412 tests in each of nine jobs**: Node 20, 22 and
|
|
154
|
+
24 on Windows, Ubuntu and macOS. Package checks install the actual tarball and
|
|
155
|
+
check imports, TypeScript declarations, examples and archive byte identity.
|
|
156
|
+
Local Chromium 156 also passed 16 consumer scenarios on Windows. The Node CI
|
|
157
|
+
matrix does not certify native browser behavior: the current runtime has not
|
|
158
|
+
been revalidated in Firefox/Safari or in browsers on the other operating systems.
|
|
159
|
+
|
|
160
|
+
The latest corpus expansion checked 23,889 events in seven real ASS/SSA files
|
|
161
|
+
for timing, raw text and fields; reading text was scored on a restricted 5,107
|
|
162
|
+
events. Three legacy SAMI files were checked separately. A ten-minute seeded
|
|
163
|
+
campaign completed 3,655,680 mutations and 456,960 arithmetic cases, with another
|
|
164
|
+
896 mutations on full-size original files. These are bounded checks, not proof
|
|
165
|
+
of perfection, full rendering conformance or superiority over every parser.
|
|
166
|
+
|
|
167
|
+
Reproduce the standard checks in a source checkout with `npm run validate`.
|
|
168
|
+
[Corpus methodology](docs/CORPUS.md), [the latest evaluation](docs/CORPUS-017.md)
|
|
169
|
+
and [quality criteria](docs/QUALITY.md) explain sources, licenses, comparisons,
|
|
170
|
+
unscored cases and reproduction commands. Historical local evidence archives
|
|
171
|
+
are not included in Git or npm.
|
|
172
|
+
|
|
173
|
+
## Contribute and report bugs
|
|
174
|
+
|
|
175
|
+
Small reproductions, corrections to format interpretation, documentation and
|
|
176
|
+
tests are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) for setup and expected
|
|
177
|
+
evidence, and [Reporting bugs](docs/REPORTING.md) for the report checklist.
|
|
178
|
+
Use the [public source repository](https://github.com/saramkim/independent-subtitle-parser),
|
|
179
|
+
[bug report form](https://github.com/saramkim/independent-subtitle-parser/issues/new?template=bug_report.yml)
|
|
180
|
+
and [pull requests](https://github.com/saramkim/independent-subtitle-parser/pulls).
|
|
181
|
+
|
|
182
|
+
During 0.x, review the [changelog](CHANGELOG.md) when upgrading and pin the
|
|
183
|
+
version you have validated. New diagnostics and recovery behavior can affect
|
|
184
|
+
consumer decisions. Full rendering, serialization, streaming and automatic
|
|
185
|
+
charset guessing remain outside this package's current scope.
|
|
186
|
+
|
|
187
|
+
## License and documentation
|
|
188
|
+
|
|
189
|
+
Original package code is licensed under [MIT](LICENSE). Third-party reference
|
|
190
|
+
data retains its own licenses; see [third-party notices](THIRD_PARTY_NOTICES.md).
|
|
191
|
+
|
|
192
|
+
- [API reference](docs/API.md) and [diagnostic guide](docs/DIAGNOSTICS.md)
|
|
193
|
+
- [Usage recipes](docs/USAGE.md) and [development guide](docs/DEVELOPMENT.md)
|
|
194
|
+
- [Design](docs/DESIGN.md), [public format sources](docs/SOURCES.md) and [changelog](CHANGELOG.md)
|
|
195
|
+
- [Release checklist for maintainers](docs/RELEASE.md)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# WHATWG HTML named character reference data
|
|
2
|
+
|
|
3
|
+
Original parser code is under the MIT license in `LICENSE`. This does not
|
|
4
|
+
relicense third-party data. Additional development-only fixtures in
|
|
5
|
+
`corpus/release-015/` are from libass (ISC; local `libass/COPYING`) and Microsoft
|
|
6
|
+
Windows-classic-samples (MIT; local `microsoft/LICENSE`), with exact revisions,
|
|
7
|
+
URLs and hashes in that directory's manifest. These fixtures are not in the
|
|
8
|
+
npm tarball. The external sample/writer provenance and coverage limits are
|
|
9
|
+
documented in `docs/RELEASE.md`.
|
|
10
|
+
|
|
11
|
+
Development-only `corpus/corpus-016/` adds unchanged authored samples from
|
|
12
|
+
Arnavion/libjass (Apache-2.0), subtitle-rs/subtitler (Apache-2.0), and
|
|
13
|
+
SubtitleEdit/subtitleedit (MIT). Each upstream license is retained alongside
|
|
14
|
+
its samples; `manifest.json` pins revisions, original URLs and SHA-256 hashes.
|
|
15
|
+
No upstream parser implementation, font or image is incorporated. These samples
|
|
16
|
+
and licenses are excluded from the npm tarball. Additional downloaded originals
|
|
17
|
+
with uncertain transcript redistribution rights remain in ignored local caches;
|
|
18
|
+
their metadata alone is in `research/corpus-016/local-manifest.json`.
|
|
19
|
+
|
|
20
|
+
Development-only corpus: WPT files retain their BSD-3-Clause notice in
|
|
21
|
+
`corpus/wpt/LICENSE.md`. *Tears of Steel* subtitles are attributed to
|
|
22
|
+
**(CC) Blender Foundation | mango.blender.org** and volunteer translators under
|
|
23
|
+
CC BY 3.0 (https://creativecommons.org/licenses/by/3.0/), with unchanged original
|
|
24
|
+
files and provenance documented in `docs/CORPUS.md`. Corpus and comparison
|
|
25
|
+
dependencies are not shipped in the npm tarball. No third-party parser code is
|
|
26
|
+
incorporated into the runtime. The small windows-1252 mapping in `decode.ts` is
|
|
27
|
+
standard character data from https://encoding.spec.whatwg.org/index-windows-1252.txt
|
|
28
|
+
under WHATWG's BSD terms reproduced below.
|
|
29
|
+
|
|
30
|
+
`entities.reference.json` was retrieved on 2026-10-23 from
|
|
31
|
+
https://html.spec.whatwg.org/entities.json . `src/entities.ts` is a mechanical
|
|
32
|
+
projection of the names and Unicode values. No parser code
|
|
33
|
+
was copied. `scripts/generate-entities.mjs` reproduces that projection.
|
|
34
|
+
|
|
35
|
+
Copyright © WHATWG (Apple, Google, Mozilla, Microsoft).
|
|
36
|
+
The HTML standard portions incorporated into source code use the BSD 3-Clause
|
|
37
|
+
License, per https://whatwg.org/ipr-policy#copyright-license-for-whatwg-publications .
|
|
38
|
+
|
|
39
|
+
Redistribution and use in source and binary forms, with or without
|
|
40
|
+
modification, are permitted provided that the following conditions are met:
|
|
41
|
+
|
|
42
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
43
|
+
list of conditions and the following disclaimer.
|
|
44
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
45
|
+
this list of conditions and the following disclaimer in the documentation
|
|
46
|
+
and/or other materials provided with the distribution.
|
|
47
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
48
|
+
may be used to endorse or promote products derived from this software without
|
|
49
|
+
specific prior written permission.
|
|
50
|
+
|
|
51
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
52
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
53
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
54
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
55
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
56
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
57
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
58
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
59
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
60
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Cue, Limits, MetadataBlock, ParseResult, Severity, SubtitleFormat, Track } from './types.js';
|
|
2
|
+
export declare const DEFAULT_LIMITS: Readonly<Limits>;
|
|
3
|
+
export declare class ParseAbort extends Error {
|
|
4
|
+
}
|
|
5
|
+
export declare class ParseContext {
|
|
6
|
+
readonly format: SubtitleFormat;
|
|
7
|
+
readonly limits: Limits;
|
|
8
|
+
readonly cues: Cue[];
|
|
9
|
+
readonly tracks: Track[];
|
|
10
|
+
readonly metadata: MetadataBlock[];
|
|
11
|
+
private problem;
|
|
12
|
+
private truncated;
|
|
13
|
+
private readonly infoCodes;
|
|
14
|
+
private readonly diagnosticBuckets;
|
|
15
|
+
private diagnosticCount;
|
|
16
|
+
private diagnosticOrder;
|
|
17
|
+
constructor(format: SubtitleFormat, limits: Limits);
|
|
18
|
+
diagnostic(code: string, severity: Severity, message: string, line?: number): void;
|
|
19
|
+
fatal(code: string, message: string, line?: number): never;
|
|
20
|
+
addCue(cue: Cue): boolean;
|
|
21
|
+
result(sourceText: string, fatal?: boolean): ParseResult;
|
|
22
|
+
}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export const DEFAULT_LIMITS = Object.freeze({
|
|
2
|
+
maxInputLength: 8 * 1024 * 1024, maxLines: 250_000, maxLineLength: 1024 * 1024,
|
|
3
|
+
maxCues: 100_000, maxDiagnostics: 1_000,
|
|
4
|
+
});
|
|
5
|
+
export class ParseAbort extends Error {
|
|
6
|
+
}
|
|
7
|
+
const severityRank = { info: 0, warning: 1, error: 2, fatal: 3 };
|
|
8
|
+
export class ParseContext {
|
|
9
|
+
format;
|
|
10
|
+
limits;
|
|
11
|
+
cues = [];
|
|
12
|
+
tracks = [];
|
|
13
|
+
metadata = [];
|
|
14
|
+
problem = false;
|
|
15
|
+
truncated = false;
|
|
16
|
+
infoCodes = new Set();
|
|
17
|
+
diagnosticBuckets = [[], [], [], []];
|
|
18
|
+
diagnosticCount = 0;
|
|
19
|
+
diagnosticOrder = 0;
|
|
20
|
+
constructor(format, limits) {
|
|
21
|
+
this.format = format;
|
|
22
|
+
this.limits = limits;
|
|
23
|
+
}
|
|
24
|
+
diagnostic(code, severity, message, line) {
|
|
25
|
+
if (severity === 'info') {
|
|
26
|
+
if (this.infoCodes.has(code))
|
|
27
|
+
return;
|
|
28
|
+
this.infoCodes.add(code);
|
|
29
|
+
}
|
|
30
|
+
if (severity === 'warning' || severity === 'error' || severity === 'fatal')
|
|
31
|
+
this.problem = true;
|
|
32
|
+
const rank = severityRank[severity];
|
|
33
|
+
if (this.diagnosticCount >= this.limits.maxDiagnostics) {
|
|
34
|
+
this.truncated = true;
|
|
35
|
+
this.problem = true;
|
|
36
|
+
// Four fixed buckets keep overflow work O(1), even for large budgets.
|
|
37
|
+
const lowest = this.diagnosticBuckets.findIndex(bucket => bucket.length > 0);
|
|
38
|
+
if (rank <= lowest && severity !== 'fatal')
|
|
39
|
+
return;
|
|
40
|
+
// Keep earlier diagnostics at equal severity; replace the newest member
|
|
41
|
+
// of a lower severity when an actionable cause arrives.
|
|
42
|
+
this.diagnosticBuckets[lowest].pop();
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
this.diagnosticCount++;
|
|
46
|
+
const diagnostic = { code, severity, message, ...(line === undefined ? {} : { line }) };
|
|
47
|
+
this.diagnosticBuckets[rank].push({ diagnostic, order: this.diagnosticOrder++ });
|
|
48
|
+
}
|
|
49
|
+
fatal(code, message, line) {
|
|
50
|
+
this.diagnostic(code, 'fatal', message, line);
|
|
51
|
+
throw new ParseAbort(message);
|
|
52
|
+
}
|
|
53
|
+
addCue(cue) {
|
|
54
|
+
if (!Number.isSafeInteger(cue.start) || cue.start < 0 ||
|
|
55
|
+
(cue.end !== null && (!Number.isSafeInteger(cue.end) || cue.end <= cue.start))) {
|
|
56
|
+
this.diagnostic('INVALID_INTERVAL', 'error', 'Cue requires safe nonnegative integer milliseconds and end greater than start.', cue.source.line);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (this.cues.length >= this.limits.maxCues)
|
|
60
|
+
this.fatal('CUE_LIMIT', 'Cue count exceeds maxCues.', cue.source.line);
|
|
61
|
+
this.cues.push(cue);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
result(sourceText, fatal = false) {
|
|
65
|
+
const diagnostics = this.diagnosticBuckets.flat().sort((a, b) => a.order - b.order).map(entry => entry.diagnostic);
|
|
66
|
+
return { format: this.format, status: fatal ? 'fatal' : this.problem ? 'partial' : 'ok',
|
|
67
|
+
cues: fatal ? [] : this.cues, tracks: fatal ? [] : this.tracks,
|
|
68
|
+
metadata: fatal ? [] : this.metadata, diagnostics, diagnosticsTruncated: this.truncated, sourceText };
|
|
69
|
+
}
|
|
70
|
+
}
|
package/dist/decode.d.ts
ADDED
package/dist/decode.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Intrinsic getters inspect internal typed-array slots across realms. Object#toString
|
|
2
|
+
// and an object's public byteLength can both be overridden by callers.
|
|
3
|
+
const typedArrayPrototype = Object.getPrototypeOf(Uint8Array.prototype);
|
|
4
|
+
const typedArrayName = Object.getOwnPropertyDescriptor(typedArrayPrototype, Symbol.toStringTag).get;
|
|
5
|
+
const typedArrayByteLength = Object.getOwnPropertyDescriptor(typedArrayPrototype, 'byteLength').get;
|
|
6
|
+
// WHATWG windows-1252 index entries 0..31 (bytes 0x80..0x9f).
|
|
7
|
+
// Some Node/ICU versions incorrectly return Latin-1 controls for these bytes.
|
|
8
|
+
// A conforming decoder's five literal C1 controls map to themselves here.
|
|
9
|
+
const WINDOWS_1252_C1 = '€\u0081‚ƒ„…†‡ˆ‰Š‹Œ\u008dŽ\u008f\u0090‘’“”•–—˜™š›œ\u009džŸ';
|
|
10
|
+
/** Explicit byte decoding, independent of subtitle format and file/network I/O. */
|
|
11
|
+
export function decode(bytes, options = {}) {
|
|
12
|
+
if (!ArrayBuffer.isView(bytes) || typedArrayName.call(bytes) !== 'Uint8Array')
|
|
13
|
+
throw new TypeError('decode expects Uint8Array.');
|
|
14
|
+
const maxBytes = options.maxBytes ?? 16 * 1024 * 1024;
|
|
15
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1)
|
|
16
|
+
throw new RangeError('maxBytes must be a positive safe integer.');
|
|
17
|
+
if (options.fatal !== undefined && typeof options.fatal !== 'boolean')
|
|
18
|
+
throw new TypeError('fatal must be boolean.');
|
|
19
|
+
const diagnostics = [];
|
|
20
|
+
const fail = (code, message, encoding = null, bom = null) => ({ text: null, encoding, bom, diagnostics: [...diagnostics, { code, severity: 'fatal', message }] });
|
|
21
|
+
if (typedArrayByteLength.call(bytes) > maxBytes)
|
|
22
|
+
return fail('BYTE_LIMIT', 'Byte input exceeds maxBytes.');
|
|
23
|
+
// UTF-32 must not be mistaken for UTF-16LE, and is unsupported by TextDecoder.
|
|
24
|
+
if ((bytes[0] === 0xff && bytes[1] === 0xfe && bytes[2] === 0 && bytes[3] === 0) ||
|
|
25
|
+
(bytes[0] === 0 && bytes[1] === 0 && bytes[2] === 0xfe && bytes[3] === 0xff))
|
|
26
|
+
return fail('UNSUPPORTED_BOM', 'UTF-32 input is unsupported.');
|
|
27
|
+
const bom = bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf ? 'utf-8'
|
|
28
|
+
: bytes[0] === 0xff && bytes[1] === 0xfe ? 'utf-16le' : bytes[0] === 0xfe && bytes[1] === 0xff ? 'utf-16be' : null;
|
|
29
|
+
let decoder;
|
|
30
|
+
try {
|
|
31
|
+
decoder = new TextDecoder(options.encoding ?? bom ?? 'utf-8', { fatal: true });
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return fail('UNSUPPORTED_ENCODING', 'Encoding label is unsupported by this runtime.', null, bom);
|
|
35
|
+
}
|
|
36
|
+
const encoding = decoder.encoding;
|
|
37
|
+
if (bom && encoding !== bom)
|
|
38
|
+
return fail('ENCODING_BOM_CONFLICT', 'Explicit encoding conflicts with the byte order mark.', encoding, bom);
|
|
39
|
+
try {
|
|
40
|
+
let text = decoder.decode(bytes);
|
|
41
|
+
if (encoding === 'windows-1252')
|
|
42
|
+
text = text.replace(/[\u0080-\u009f]/g, char => WINDOWS_1252_C1[char.charCodeAt(0) - 0x80]);
|
|
43
|
+
return { text, encoding, bom, diagnostics };
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
if (options.fatal !== false)
|
|
47
|
+
return fail('INVALID_ENCODING', 'Bytes are invalid for the selected encoding. Choose the correct encoding explicitly.', encoding, bom);
|
|
48
|
+
diagnostics.push({ code: 'DECODE_REPLACEMENT', severity: 'warning', message: 'Invalid byte sequences were replaced with U+FFFD at explicit caller request.' });
|
|
49
|
+
return { text: new TextDecoder(encoding).decode(bytes), encoding, bom, diagnostics };
|
|
50
|
+
}
|
|
51
|
+
}
|
package/dist/detect.d.ts
ADDED
package/dist/detect.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Content-only heuristic, examining at most 64 Ki UTF-16 code units. No filename guessing. */
|
|
2
|
+
export function detectFormat(text) {
|
|
3
|
+
if (typeof text !== 'string')
|
|
4
|
+
throw new TypeError('detectFormat expects a string.');
|
|
5
|
+
const sample = text.slice(0, 65_536).replace(/^\uFEFF/, '');
|
|
6
|
+
if (/^WEBVTT(?:[ \t\r\n]|$)/.test(sample))
|
|
7
|
+
return { format: 'vtt', confidence: 'high', candidates: ['vtt'] };
|
|
8
|
+
let offset = 0;
|
|
9
|
+
while (offset < sample.length) {
|
|
10
|
+
while (offset < sample.length && /\s/.test(sample[offset]))
|
|
11
|
+
offset++;
|
|
12
|
+
if (!sample.startsWith('<!--', offset))
|
|
13
|
+
break;
|
|
14
|
+
const end = sample.indexOf('-->', offset + 4);
|
|
15
|
+
if (end < 0)
|
|
16
|
+
break;
|
|
17
|
+
offset = end + 3;
|
|
18
|
+
}
|
|
19
|
+
const leading = sample.slice(offset);
|
|
20
|
+
if (/^<sami(?:\s|>)/i.test(leading))
|
|
21
|
+
return { format: 'sami', confidence: 'high', candidates: ['sami'] };
|
|
22
|
+
if (/^\[(?:Script Info|Events|V4\+? Styles)\]/i.test(leading)) {
|
|
23
|
+
const ssa = /(?:^|[\r\n])[ \t]*(?:ScriptType[ \t]*:[ \t]*v4\.00[ \t]*$|\[V4 Styles\])/im.test(sample);
|
|
24
|
+
const ass = /(?:^|[\r\n])[ \t]*(?:ScriptType[ \t]*:[ \t]*v4\.00\+|\[V4\+ Styles\])/im.test(sample);
|
|
25
|
+
if (ssa && ass)
|
|
26
|
+
return { format: null, confidence: 'ambiguous', candidates: ['ass', 'ssa'] };
|
|
27
|
+
return { format: ssa ? 'ssa' : 'ass', confidence: ass || ssa ? 'high' : 'low', candidates: [ssa ? 'ssa' : 'ass'] };
|
|
28
|
+
}
|
|
29
|
+
if (/(?:^|[\r\n])[ \t]*\d+:\d{2}:\d{2},\d{3}[ \t]+-->/.test(sample))
|
|
30
|
+
return { format: 'srt', confidence: 'high', candidates: ['srt'] };
|
|
31
|
+
if (/(?:^|[\r\n])[ \t]*\d+:\d{2}:\d{2},\d{3}[ \t]+->/.test(sample))
|
|
32
|
+
return { format: 'srt', confidence: 'low', candidates: ['srt'] };
|
|
33
|
+
if (/(?:^|[\r\n])[ \t]*(?:\d+:)?\d{2}:\d{2}\.\d{3}[ \t]+-->/.test(sample))
|
|
34
|
+
return { format: null, confidence: 'ambiguous', candidates: ['srt', 'vtt'] };
|
|
35
|
+
if (/<sync\b[^<>]*\bstart\s*=/i.test(sample))
|
|
36
|
+
return { format: 'sami', confidence: 'low', candidates: ['sami'] };
|
|
37
|
+
return { format: null, confidence: 'none', candidates: [] };
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const HTML_ENTITIES: Readonly<Record<string, string>>;
|