subgapfix 0.0.2__py3-none-any.whl → 0.0.3__py3-none-any.whl

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.
subgapfix/subgapfix.py CHANGED
@@ -1,51 +1,163 @@
1
- import argparse
2
- import srt
3
- from datetime import timedelta
4
-
5
- def extend_subs(input_file, output_file, extend_sub_start=0.5, extend_sub_end=2.0, min_gap=1.0):
6
-
7
- if min_gap > extend_sub_start:
8
-
9
- with open(input_file, "r", encoding="utf-8") as f:
10
- subs = list(srt.parse(f.read()))
11
-
12
- for i in range(len(subs) - 1):
13
- first, second = subs[i], subs[i + 1]
14
- gap = (second.start - first.end).total_seconds()
15
-
16
- if gap < min_gap:
17
- first.end += timedelta(seconds=gap/2)
18
- second.start -= timedelta(seconds=gap/2)
19
-
20
- if gap >= min_gap:
21
- extendable = gap - extend_sub_start
22
- if extendable > 0:
23
- extension = min(extendable, extend_sub_end)
24
- first.end += timedelta(seconds=extension)
25
- second.start -= timedelta(seconds=extend_sub_start)
26
-
27
- # Make sure subs don't overlap each other
28
- if second.start <= first.end:
29
- second.start = first.end + timedelta(milliseconds=10)
30
-
31
- with open(output_file, "w", encoding="utf-8") as f:
32
- f.write(srt.compose(subs))
33
-
34
- else:
35
- print("Hold up! The value of min_gap should be greater than that of extended_sub_start.")
36
-
37
- def main():
38
- parser = argparse.ArgumentParser(
39
- description="Extend subtitle durations when there is a gap before the next subtitle."
40
- )
41
- parser.add_argument("input", help="Input SRT file")
42
- parser.add_argument("-o", "--output", default="subgapfix.srt", help="Output SRT file")
43
- parser.add_argument("--extend-sub-start", "-ess", type=float, default=0.5, help="Seconds to add to start of subtitle. Default is: -ess 0.5")
44
- parser.add_argument("--extend-sub-end", "-ese", type=float, default=2.0, help="Seconds to add to end of subtitle. Default is: -ese 2.0")
45
- parser.add_argument("--min-gap", "-mg", type=float, default=1.0, help="Minimum gap (in seconds) required to apply extension. Default is: -mg 1.0")
46
- args = parser.parse_args()
47
-
48
- extend_subs(args.input, args.output, args.extend_sub_start, args.extend_sub_end, args.min_gap)
49
-
50
- if __name__ == "__main__":
51
- main()
1
+ from pathlib import Path
2
+ import srt
3
+ from datetime import timedelta
4
+ import typer
5
+
6
+
7
+ app = typer.Typer(
8
+ help="Extend subtitle display durations in gaps (better readability without overlaps).",
9
+ add_completion=True,
10
+ )
11
+
12
+
13
+ def validate_input_file(input_file: Path) -> None:
14
+ """Check if the input is a valid .srt file."""
15
+ if input_file.suffix.lower() != ".srt":
16
+ typer.secho(
17
+ f"Error: Input file must have .srt extension (got: {input_file.name})",
18
+ fg=typer.colors.RED,
19
+ err=True,
20
+ )
21
+ raise typer.Exit(1)
22
+
23
+
24
+ def prepare_output_path(input_file: Path, output: Path | None) -> Path:
25
+ """Determine output path — use default if not provided."""
26
+ if output is None:
27
+ return input_file.with_stem(input_file.stem + "_fixed")
28
+ return output
29
+
30
+
31
+ def validate_parameters(extend_start: float, min_gap: float, extend_final_sub: float) -> None:
32
+ """Ensure logical parameter constraints."""
33
+ if min_gap <= extend_start:
34
+ typer.secho(
35
+ f"Error: --min-gap ({min_gap}) must be > --extend-start ({extend_start})",
36
+ fg=typer.colors.RED,
37
+ err=True,
38
+ )
39
+ raise typer.Exit(1)
40
+
41
+ if extend_final_sub < 0:
42
+ typer.secho(
43
+ f"Error: --extend-final-sub must be greater than or equal to 0",
44
+ fg=typer.colors.RED,
45
+ err=True,
46
+ )
47
+ raise typer.Exit(1)
48
+
49
+
50
+ def load_subtitles(input_file: Path) -> list[srt.Subtitle]:
51
+ """Read and parse the SRT file."""
52
+ try:
53
+ content = input_file.read_text(encoding="utf-8")
54
+ return list(srt.parse(content))
55
+ except Exception as e:
56
+ typer.secho(f"Cannot parse SRT: {e}", fg=typer.colors.RED, err=True)
57
+ raise typer.Exit(1)
58
+
59
+
60
+ def extend_gaps(
61
+ subs: list[srt.Subtitle],
62
+ extend_start: float,
63
+ extend_end_max: float,
64
+ min_gap: float,
65
+ extend_final_sub: float
66
+ ) -> int:
67
+ """Apply gap extension logic. Returns number of changed pairs."""
68
+ changes = 0
69
+
70
+ for i in range(len(subs) - 1):
71
+ a, b = subs[i], subs[i + 1]
72
+ gap = (b.start - a.end).total_seconds()
73
+
74
+ old_a_end = a.end
75
+ old_b_start = b.start
76
+
77
+ if gap < min_gap:
78
+ delta = gap / 2
79
+ a.end += timedelta(seconds=delta)
80
+ b.start -= timedelta(seconds=delta)
81
+ elif gap >= min_gap:
82
+ extendable = gap - extend_start
83
+ if extendable > 0:
84
+ extension = min(extendable, extend_end_max)
85
+ a.end += timedelta(seconds=extension)
86
+ b.start -= timedelta(seconds=extend_start)
87
+
88
+ # Prevent overlap / negative duration
89
+ if b.start <= a.end:
90
+ b.start = a.end + timedelta(milliseconds=10)
91
+
92
+ if a.end != old_a_end or b.start != old_b_start:
93
+ changes += 1
94
+
95
+ # Finally give the very last subtitle +1 second
96
+ if subs and extend_final_sub > 0:
97
+ last = subs[-1]
98
+ last.end += timedelta(seconds=extend_final_sub)
99
+ changes += 1
100
+
101
+ return changes
102
+
103
+
104
+ @app.command()
105
+ def main(
106
+
107
+ input_file: Path = typer.Argument(..., exists=True, dir_okay=False),
108
+
109
+ output: Path = typer.Option(
110
+ None,
111
+ "--output",
112
+ "-o",
113
+ help="Output file. Default: <input>_fixed.srt in same folder",
114
+ ),
115
+
116
+ extend_start: float = typer.Option(
117
+ 0.5, "--extend-sub-start", "-ess", help="Seconds to pull next sub backward"
118
+ ),
119
+
120
+ extend_end_max: float = typer.Option(
121
+ 2.0, "--extend-sub-end", "-ese", help="Max seconds to extend current sub forward"
122
+ ),
123
+
124
+ min_gap: float = typer.Option(
125
+ 1.0, "--min-gap", "-mg", help="Only extend when gap ≥ this value"
126
+ ),
127
+
128
+ dry_run: bool = typer.Option(False, "--dry-run", help="Show amount changes without writing"),
129
+
130
+ extend_final_sub: float = typer.Option(
131
+ 1.0,
132
+ "--extend-final-sub",
133
+ "-efs",
134
+ help="Seconds to add to the very last subtitle"
135
+ )
136
+ ):
137
+
138
+ validate_input_file(input_file)
139
+
140
+ output_path = prepare_output_path(input_file, output)
141
+
142
+ if not dry_run:
143
+ output_path.parent.mkdir(parents=True, exist_ok=True)
144
+
145
+ validate_parameters(extend_start, min_gap, extend_final_sub)
146
+
147
+ subs = load_subtitles(input_file)
148
+
149
+ changes = extend_gaps(subs, extend_start, extend_end_max, min_gap, extend_final_sub)
150
+
151
+ if dry_run:
152
+ typer.echo(f"The dry run ran succesfully and detected {changes} changes to subtitle pairs.")
153
+ return
154
+
155
+ output_path.write_text(srt.compose(subs), encoding="utf-8")
156
+ typer.secho(
157
+ f"Done. Wrote {len(subs)} subtitles → {output_path}",
158
+ fg=typer.colors.GREEN,
159
+ )
160
+
161
+
162
+ if __name__ == "__main__":
163
+ app()
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: subgapfix
3
+ Version: 0.0.3
4
+ Summary: A small CLI tool to extend subtitle durations in SRT files to make them more readable on a screen.
5
+ Author-email: Reinder Sinnema <reinder@w3bunker.com>
6
+ License: MIT
7
+ Requires-Python: >=3.8
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: srt>=3.5.3
10
+ Requires-Dist: typer>=0.21.1
11
+
12
+ # SubGapFix
13
+
14
+ [![PyPI version](https://img.shields.io/pypi/v/subgapfix.svg)](https://pypi.org/project/subgapfix/)
15
+ [![Python versions](https://img.shields.io/pypi/pyversions/subgapfix.svg)](https://pypi.org/project/subgapfix/)
16
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
17
+
18
+ **SubGapFix** is a lightweight CLI tool that makes subtitles easier and more comfortable to read by **intelligently extending their display duration** — especially useful for auto-generated subtitles.
19
+
20
+ It works great with transcriptions from **[WhisperX](https://github.com/m-bain/whisperX)**.
21
+
22
+ ## ✨ Features
23
+
24
+ - Extends subtitle display time when there is a **gap** before the next line
25
+ - Safely prevents overlaps and negative durations
26
+ - Dry-run mode to preview changes without writing files
27
+ - Creates output folders automatically if needed
28
+ - Only processes valid `.srt` files
29
+
30
+ ## 📦 Installation
31
+
32
+ ```bash
33
+ pip install subgapfix
34
+ ```
35
+
36
+ Requires Python 3.8+.
37
+
38
+ Dependencies:
39
+ - `typer` — beautiful CLI interface
40
+ - `srt` — reliable SRT parsing
41
+
42
+ ## 🚀 Quick Start
43
+
44
+ ```bash
45
+ # Basic usage — creates episode_fixed.srt in the same folder
46
+ subgapfix episode.srt
47
+
48
+ # Custom output file (creates folders if needed)
49
+ subgapfix podcast/episode.srt -o podcast/enhanced/episode_subtitles.srt
50
+
51
+ # Preview changes without modifying anything
52
+ subgapfix input.srt --dry-run
53
+
54
+ # Customize timings
55
+ subgapfix input.srt --extend-sub-start 0.8 --extend-sub-end 3.0 --min-gap 1.5 --extend-final-sub 4.5
56
+ ```
57
+
58
+ ## ⚙️ All Options
59
+
60
+ | Flag / Short | Default | Description |
61
+ |--------------|---------|-------------|
62
+ | `input_file` (positional) | — | Path to the input `.srt` file (must exist) |
63
+ | `-o, --output` | `<input>_fixed.srt` | Output file path (folders auto-created) |
64
+ | `--extend-sub-start`, `-ess` | `0.5` | Seconds to pull the **next** subtitle backward (tries to borrow from gap) |
65
+ | `--extend-sub-end`, `-ese` | `2.0` | Maximum seconds to extend current subtitle forward into the gap |
66
+ | `--min-gap`, `-mg` | `1.0` | Only apply extension if gap is at least this long (seconds) |
67
+ | `--dry-run` | `false` | Show how many pairs would change — no file written |
68
+ | `--extend-final-sub`, `-efs` | `1.0` | Number of seconds to add to the last subtitle |
69
+ | `--help` | — | Show full help and exit |
70
+
71
+ ## How It Works
72
+
73
+ 1. Reads and validates the `.srt` file
74
+ 2. For each pair of consecutive subtitles:
75
+ - If gap < `--min-gap` → splits the gap evenly (closes small gaps)
76
+ - If gap ≥ `--min-gap` → extends current subtitle up to `--extend-end-max`, pulls next one back by `--extend-start`
77
+ 3. Prevents any overlap by enforcing a tiny 10 ms safety gap
78
+ 4. Add `--extend-final-sub` to the last subtitle for a precise ending
79
+ 5. Writes the result (or just reports changes in dry-run)
80
+
81
+ **Before** (typical WhisperX-style tight timings):
82
+
83
+ ```
84
+ 1
85
+ 00:00:01,837 --> 00:00:02,502
86
+ SubGapFix makes our lives
87
+
88
+ 2
89
+ 00:00:03,147 --> 00:00:03,571
90
+ much easier.
91
+
92
+ 3
93
+ 00:00:04,176 --> 00:00:04,518
94
+ Yes, I agree.
95
+ ```
96
+
97
+ **After** (with defaults):
98
+
99
+ ```
100
+ 1
101
+ 00:00:01,837 --> 00:00:02,824
102
+ SubGapFix makes our lives
103
+
104
+ 2
105
+ 00:00:02,834 --> 00:00:03,873
106
+ much easier.
107
+
108
+ 3
109
+ 00:00:03,883 --> 00:00:04,679
110
+ Yes, I agree.
111
+ ```
112
+
113
+ ## 💡 Why SubGapFix?
114
+
115
+ Modern speech-to-text tools often prioritize transcription accuracy over comfortable reading speed.
116
+ Subtitles flash by too quickly → viewers miss text or feel rushed.
117
+
118
+ SubGapFix **does not shift timings** or re-sync — it only **lengthens display time** using existing gaps.
119
+ This keeps perfect sync with the video while making subtitles far more readable.
120
+
121
+ Perfect for lectures, interviews, conversations, podcasts.
@@ -0,0 +1,7 @@
1
+ subgapfix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ subgapfix/subgapfix.py,sha256=6Teg69GW88Y2MeegJA-YIm5pSiylGI1dFsULzgTE_EU,4709
3
+ subgapfix-0.0.3.dist-info/METADATA,sha256=zK7agcIXqb6GYWUHPoIBJgOG6L3aveXCq4CaRxfYYGU,3999
4
+ subgapfix-0.0.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
5
+ subgapfix-0.0.3.dist-info/entry_points.txt,sha256=JhP4WR6kiSkl8dE77M5WpByoX8deTaHUiTC-xdwl8t8,54
6
+ subgapfix-0.0.3.dist-info/top_level.txt,sha256=1Kmf1LI51trmbRihayDZRnKSCfEQDR7jSyBELfkwTa8,10
7
+ subgapfix-0.0.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ subgapfix = subgapfix.subgapfix:app
@@ -1,91 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: subgapfix
3
- Version: 0.0.2
4
- Summary: A small CLI tool to extend subtitle durations in SRT files by redefining gaps between subtitles.
5
- Author-email: Reinder Sinnema <reinder@w3bunker.com>
6
- License: MIT
7
- Requires-Python: >=3.8
8
- Description-Content-Type: text/markdown
9
- Requires-Dist: srt>=3.5.3
10
-
11
- # SubGapFix
12
-
13
- A small Python CLI tool to **extend subtitle durations** in `.srt` files so they stay on screen longer and feel more natural to read.
14
- Designed especially for subtitles generated by [WhisperX](https://github.com/m-bain/whisperX), which often have very tight timings.
15
-
16
- ## ✨ Features
17
-
18
- - Automatically extends subtitles when there’s a gap before the next subtitle.
19
- - Prevents overlaps by adjusting start/end times safely.
20
- - Configurable via CLI arguments.
21
-
22
- ## 📦 Installation
23
-
24
- Install from PyPI:
25
-
26
- ```bash
27
- pip install subgapfix
28
- ````
29
-
30
- Dependencies used:
31
-
32
- * [`srt`](https://pypi.org/project/srt/)
33
-
34
- ## 🔧 Usage
35
-
36
- Basic usage:
37
-
38
- ```bash
39
- subgapfix input.srt -o easyreading.srt
40
- ```
41
-
42
- ### Options
43
-
44
- | Option | Default | Description |
45
- |----------------------------|-----------------|---------------------------------------------------------------------------|
46
- | `-o, --output` | `subgapfix.srt` | Output SRT file |
47
- | `--extend-sub-start, -ess` | `0.5` | Seconds to add to start of subtitle. Default is `0.5` |
48
- | `--extend-sub-end, -ese` | `2.0` | Maximum seconds to add to end of subtitle. Default is `2.0` |
49
- | `--min-gap, -mg` | `1.0` | Minimum gap (in seconds) required to apply extension. Default is `1.0` |
50
-
51
-
52
- ### How It Works
53
-
54
- The script reads your `.srt` file, loops through each pair of consecutive subtitles, and lengthens their timings (if applicable) to make them easier to read.
55
-
56
- ### Example
57
-
58
- Input:
59
-
60
- ```
61
- 1
62
- 00:00:01,000 --> 00:00:03,000
63
- Hello there.
64
-
65
- 2
66
- 00:00:06,000 --> 00:00:08,000
67
- How are you?
68
- ```
69
-
70
- Run:
71
-
72
- ```bash
73
- subgapfix input.srt -o easyreading.srt
74
- ```
75
-
76
- Output:
77
-
78
- ```
79
- 1
80
- 00:00:01,000 --> 00:00:05,000
81
- Hello there.
82
-
83
- 2
84
- 00:00:05,500 --> 00:00:08,000
85
- How are you?
86
- ```
87
-
88
- ## 💡 Why?
89
-
90
- Tools like WhisperX produce accurate subtitles, but their timings are often too **tight** for comfortable reading.
91
- `subgapfix` helps subtitles stay visible longer while keeping synchronization intact.
@@ -1,7 +0,0 @@
1
- subgapfix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- subgapfix/subgapfix.py,sha256=tDDn4cqL-jw9KfPK4tkVaxBLVh_ZZ53xWCwi6PWTbDo,2254
3
- subgapfix-0.0.2.dist-info/METADATA,sha256=C0B7C_vEXjkdh8-dP5kYqCCI9Y_XEEzS6k56HdU1Wyg,2459
4
- subgapfix-0.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
- subgapfix-0.0.2.dist-info/entry_points.txt,sha256=2ln9RkMCsMtg0kFARJgoY7JKxBR4cEzxj4s-bKGCVUw,55
6
- subgapfix-0.0.2.dist-info/top_level.txt,sha256=1Kmf1LI51trmbRihayDZRnKSCfEQDR7jSyBELfkwTa8,10
7
- subgapfix-0.0.2.dist-info/RECORD,,
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- subgapfix = subgapfix.subgapfix:main