VideoSpeed 1.0.5__tar.gz → 1.0.9__tar.gz
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.
- videospeed-1.0.9/PKG-INFO +203 -0
- videospeed-1.0.9/README.md +186 -0
- {videospeed-1.0.5 → videospeed-1.0.9}/pyproject.toml +4 -4
- videospeed-1.0.9/src/VideoSpeed/__init__.py +6 -0
- {videospeed-1.0.5 → videospeed-1.0.9}/src/VideoSpeed/__main__.py +3 -1
- videospeed-1.0.9/src/VideoSpeed/about_page.py +201 -0
- videospeed-1.0.9/src/VideoSpeed/check_system.py +200 -0
- videospeed-1.0.9/src/VideoSpeed/cli.py +557 -0
- videospeed-1.0.9/src/VideoSpeed/edit_walk.py +428 -0
- videospeed-1.0.9/src/VideoSpeed/encoder.py +324 -0
- videospeed-1.0.9/src/VideoSpeed/file_stage.py +73 -0
- videospeed-1.0.9/src/VideoSpeed/media_info.py +67 -0
- videospeed-1.0.9/src/VideoSpeed/menu_model.py +211 -0
- videospeed-1.0.9/src/VideoSpeed/menu_painter.py +269 -0
- videospeed-1.0.9/src/VideoSpeed/menu_session.py +75 -0
- videospeed-1.0.9/src/VideoSpeed/run_output.py +110 -0
- videospeed-1.0.9/src/VideoSpeed/tui.py +387 -0
- videospeed-1.0.9/src/VideoSpeed.egg-info/PKG-INFO +203 -0
- videospeed-1.0.9/src/VideoSpeed.egg-info/SOURCES.txt +36 -0
- videospeed-1.0.9/src/VideoSpeed.egg-info/requires.txt +2 -0
- videospeed-1.0.9/tests/test_about.py +260 -0
- videospeed-1.0.9/tests/test_build.py +252 -0
- videospeed-1.0.9/tests/test_cli.py +452 -0
- videospeed-1.0.9/tests/test_dependencies.py +89 -0
- videospeed-1.0.9/tests/test_docs.py +65 -0
- videospeed-1.0.9/tests/test_errors.py +49 -0
- videospeed-1.0.9/tests/test_fs.py +173 -0
- videospeed-1.0.9/tests/test_json.py +132 -0
- videospeed-1.0.9/tests/test_logging.py +286 -0
- videospeed-1.0.9/tests/test_package.py +54 -0
- videospeed-1.0.9/tests/test_prereq.py +63 -0
- videospeed-1.0.9/tests/test_tui.py +678 -0
- videospeed-1.0.5/PKG-INFO +0 -150
- videospeed-1.0.5/README.md +0 -133
- videospeed-1.0.5/src/VideoSpeed/__init__.py +0 -3
- videospeed-1.0.5/src/VideoSpeed/cli.py +0 -489
- videospeed-1.0.5/src/VideoSpeed.egg-info/PKG-INFO +0 -150
- videospeed-1.0.5/src/VideoSpeed.egg-info/SOURCES.txt +0 -13
- videospeed-1.0.5/src/VideoSpeed.egg-info/requires.txt +0 -2
- {videospeed-1.0.5 → videospeed-1.0.9}/LICENSE.md +0 -0
- {videospeed-1.0.5 → videospeed-1.0.9}/setup.cfg +0 -0
- {videospeed-1.0.5 → videospeed-1.0.9}/src/VideoSpeed/cli.bootstrap-old.py +0 -0
- {videospeed-1.0.5 → videospeed-1.0.9}/src/VideoSpeed.egg-info/dependency_links.txt +0 -0
- {videospeed-1.0.5 → videospeed-1.0.9}/src/VideoSpeed.egg-info/entry_points.txt +0 -0
- {videospeed-1.0.5 → videospeed-1.0.9}/src/VideoSpeed.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: VideoSpeed
|
|
3
|
+
Version: 1.0.9
|
|
4
|
+
Summary: A command-line video editing tool built in Python for cutting, speeding up, and applying a boomerang effect to MP4 videos.
|
|
5
|
+
Author-email: Wilgat Wong <wilgat.wong@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Wilgat/VideoSpeed
|
|
8
|
+
Project-URL: Repository, https://github.com/Wilgat/VideoSpeed
|
|
9
|
+
Project-URL: Issues, https://github.com/Wilgat/VideoSpeed/issues
|
|
10
|
+
Keywords: Video Editing,Multimeida
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE.md
|
|
14
|
+
Requires-Dist: opencv-python-headless>=5.0.0.93
|
|
15
|
+
Requires-Dist: ChronicleLogger>=1.3.1
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# VideoSpeed - Cut, speed, and boomerang MP4 clips from the CLI
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+

|
|
22
|
+
[-purple.svg)](https://github.com/cloudgen/ciao)
|
|
23
|
+
[](https://github.com/Wilgat/VideoSpeed)
|
|
24
|
+
[]()
|
|
25
|
+
[](https://pypi.org/project/VideoSpeed/)
|
|
26
|
+
|
|
27
|
+
VideoSpeed cuts a time range from an MP4, changes that clip’s length (percent), and can append a reverse pass (boomerang). On a terminal, no arguments opens a text menu (edit, hello, about, Exit). Choosing edit walks through the prompts. Scripts pass `--file` / `--start` / `--end`. Encoding uses **FFmpeg**; duration probing uses **OpenCV**. The menu screen uses the default text-menu style: aligned number, verb, and note, and a rounded box along the bottom.
|
|
28
|
+
|
|
29
|
+
| You | The other role | Not this |
|
|
30
|
+
|-----|----------------|----------|
|
|
31
|
+
| Editor at a terminal or a script | FFmpeg on `PATH` (does the encode) | A website, installer, or root/sudo tool |
|
|
32
|
+
|
|
33
|
+
Package version SSOT: `src/VideoSpeed/__init__.py` (`MAJOR_VERSION`, `MINOR_VERSION`, `PATCH_VERSION` → **1.0.9**). `pyproject.toml` copies that string. Console entrypoint: `video-speed`.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- Recursive **MP4** discovery under a chosen folder
|
|
38
|
+
- Interactive **cut** (start/end seconds) with invalid-range re-prompt
|
|
39
|
+
- **Length/speed** change (**20–200%**) with A/V tempo kept in sync
|
|
40
|
+
- Optional **boomerang** (forward then reverse)
|
|
41
|
+
- **USB-safe** intermediate files (staged next to the output) and final publish via `shutil.move`
|
|
42
|
+
- **Text menu** on a terminal (`edit`, `hello`, `about`, `Exit`): columns line up, and a three-row rounded input box sits on the bottom with a status line under it
|
|
43
|
+
- **Product verbs** `help`, `about`, `hello`, `edit`, and `list-mp4`. `help` prints the same usage as `--help`. `about` and `hello` print a page and do not ask for a folder or a file. `edit` on a terminal asks for the folder, then the file. `list-mp4` lists MP4 files and does not encode. `Exit` stays on the menu. `./build.sh` verbs such as `setup` are not `video-speed` verbs
|
|
44
|
+
- **`--help`** / **`--version`**; no arguments in a terminal opens that menu
|
|
45
|
+
- Non-interactive job: `--file`, `--start`, `--end`, optional `--percent` / `--boomerang`, with or without `edit`. Any of those selectors with no product verb skips the menu
|
|
46
|
+
- Fail-closed when **FFmpeg** is missing from `PATH`, when prompts are needed without a terminal, or when `--percent` or `--boomerang` is passed without a job
|
|
47
|
+
|
|
48
|
+
## Quick Installation
|
|
49
|
+
|
|
50
|
+
### Prerequisites (system)
|
|
51
|
+
|
|
52
|
+
- **FFmpeg** on `PATH` (`ffmpeg -version`)
|
|
53
|
+
- A supported **Python** environment (see `requires-python` in `pyproject.toml`)
|
|
54
|
+
|
|
55
|
+
### From PyPI (registry channel)
|
|
56
|
+
|
|
57
|
+
Package name SSOT: **`VideoSpeed`** (`pyproject.toml` `[project].name`). Maintainer verbs are on `./build.sh`: `help`, `version`, `setup`, `clean`, `build`, `test`, `test-install`, `upload`, `git`, `tag`, `release` (`all` is the same chain as `release`). `test` runs `tests/run.sh`. `test-install` reads `[project].name`, removes that pip install when present, and installs this checkout. Empty `./build.sh` prints help and does not upload.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install VideoSpeed
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
If the index is behind a local release, install from a checkout or wheel instead (below). Live index version is shown by the PyPI badge above.
|
|
64
|
+
|
|
65
|
+
### From a git checkout (development)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone git@github.com:Wilgat/VideoSpeed.git
|
|
69
|
+
cd VideoSpeed
|
|
70
|
+
pip install -e .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This installs the console script **`video-speed`** and pulls Python deps (`opencv-python-headless>=5.0.0.93`, `ChronicleLogger>=1.3.1`).
|
|
74
|
+
|
|
75
|
+
### Local pyenv install (testing)
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
./setup.sh
|
|
79
|
+
pyenv shell 3.14
|
|
80
|
+
video-speed --version
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`setup.sh` runs `pyenv shell 3.14` and installs this checkout with pip into that interpreter. Install CPython 3.14 first when it is missing (`pyenv install 3.14`). Run `./setup.sh` again after source changes. In the shell where you want the command:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
pyenv shell 3.14
|
|
87
|
+
video-speed --help
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The console script is installed on the pyenv 3.14 prefix. `./setup.sh` changes that interpreter only. It does not install FFmpeg and it does not use root.
|
|
91
|
+
|
|
92
|
+
### From a local wheel / sdist
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# after: python -m build (or ./build.sh build)
|
|
96
|
+
pip install dist/VideoSpeed-1.0.9-py3-none-any.whl
|
|
97
|
+
# or
|
|
98
|
+
pip install dist/VideoSpeed-1.0.9.tar.gz
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Verify
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
video-speed --version
|
|
105
|
+
# or
|
|
106
|
+
python -m VideoSpeed --version
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Usage
|
|
110
|
+
|
|
111
|
+
Text menu (no arguments, in a terminal):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
video-speed
|
|
115
|
+
# or
|
|
116
|
+
python -m VideoSpeed
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The screen shows a numbered menu and a rounded input box along the bottom, as wide as the terminal, with one line to type on and a status line under the box. **edit** continues the prompts. **hello** (7) shows `Hello.`. **about** shows the product. **Exit** leaves.
|
|
120
|
+
|
|
121
|
+
The same actions are verbs. `edit` starts at the folder question. `list-mp4` asks for the folder when it is omitted, prints the numbered list, and stops.
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
video-speed help
|
|
125
|
+
video-speed about
|
|
126
|
+
video-speed hello
|
|
127
|
+
video-speed edit
|
|
128
|
+
video-speed edit --folder ./clips
|
|
129
|
+
video-speed edit --file clip.mp4 --start 1 --end 5 --percent 100
|
|
130
|
+
video-speed list-mp4
|
|
131
|
+
video-speed list-mp4 --folder ./clips
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
1. Folder containing MP4s (Enter = current directory)
|
|
135
|
+
2. Choose a video by number
|
|
136
|
+
3. Cut start/end seconds
|
|
137
|
+
4. New length percent (default 100; allowed 20–200)
|
|
138
|
+
5. Optional boomerang (y/n)
|
|
139
|
+
6. Output is written **next to the source** with a descriptive name
|
|
140
|
+
|
|
141
|
+
Non-interactive job (scripts / CI):
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
video-speed --file clip.mp4 --start 1 --end 5 --percent 100
|
|
145
|
+
video-speed --file clip.mp4 --start 1 --end 5 --percent 50 --boomerang
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Other:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
video-speed --help
|
|
152
|
+
video-speed --version
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Without a terminal and without a job, the program exits with an error instead of hanging on prompts. `--percent` or `--boomerang` alone does the same and names `--file`, `--start`, and `--end`. `--folder` checks the directory. It does not pick a file.
|
|
156
|
+
|
|
157
|
+
`--json` keeps that same choice, closes the text menu, and prints one JSON object on standard output. On a terminal with no job flags, the questions are written to the error stream. Progress is not mixed into the object.
|
|
158
|
+
|
|
159
|
+
## Examples
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Install editable, then run interactive session in a folder of clips
|
|
163
|
+
cd /path/to/videos
|
|
164
|
+
video-speed
|
|
165
|
+
|
|
166
|
+
# Check identity without starting prompts
|
|
167
|
+
video-speed --version
|
|
168
|
+
|
|
169
|
+
# One-shot job (no prompts)
|
|
170
|
+
video-speed --file clip.mp4 --start 1.0 --end 5.0 --percent 100
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Example output name shape:
|
|
174
|
+
|
|
175
|
+
```text
|
|
176
|
+
clip_cut1.0-5.0s_100pct.mp4
|
|
177
|
+
clip_cut1.0-5.0s_50pct_BOOMERANG.mp4
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Platform Compatibility
|
|
181
|
+
|
|
182
|
+
| Platform | Status |
|
|
183
|
+
|----------|--------|
|
|
184
|
+
| Linux | Primary (tested in development) |
|
|
185
|
+
| macOS / Windows | Expected to work when Python, FFmpeg, and OpenCV are available |
|
|
186
|
+
| Filesystems | Local disk and removable/USB volumes supported for output (temps staged beside final path) |
|
|
187
|
+
|
|
188
|
+
## Related Projects
|
|
189
|
+
|
|
190
|
+
- [VideoSpeed on GitHub](https://github.com/Wilgat/VideoSpeed)
|
|
191
|
+
- [VideoSpeed on PyPI](https://pypi.org/project/VideoSpeed/)
|
|
192
|
+
|
|
193
|
+
## Contributing
|
|
194
|
+
|
|
195
|
+
Fork the repository, use a checkout install (`pip install -e .` or `./setup.sh`), and open a pull request. Prefer small, tested changes. Do not strip defensive temp/publish behavior (cross-filesystem `shutil.move`) without an explicit redesign.
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
MIT License — see [`LICENSE.md`](LICENSE.md).
|
|
200
|
+
|
|
201
|
+
## Last Update
|
|
202
|
+
|
|
203
|
+
2026-10-01 — **1.0.9**: a non-TUI, non-JSON run shows `debug mode` when `DEBUG` is already on, and each class logs `instantiated` on the one ChronicleLogger. `def main` stays in `src/VideoSpeed/cli.py`.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# VideoSpeed - Cut, speed, and boomerang MP4 clips from the CLI
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
[-purple.svg)](https://github.com/cloudgen/ciao)
|
|
6
|
+
[](https://github.com/Wilgat/VideoSpeed)
|
|
7
|
+
[]()
|
|
8
|
+
[](https://pypi.org/project/VideoSpeed/)
|
|
9
|
+
|
|
10
|
+
VideoSpeed cuts a time range from an MP4, changes that clip’s length (percent), and can append a reverse pass (boomerang). On a terminal, no arguments opens a text menu (edit, hello, about, Exit). Choosing edit walks through the prompts. Scripts pass `--file` / `--start` / `--end`. Encoding uses **FFmpeg**; duration probing uses **OpenCV**. The menu screen uses the default text-menu style: aligned number, verb, and note, and a rounded box along the bottom.
|
|
11
|
+
|
|
12
|
+
| You | The other role | Not this |
|
|
13
|
+
|-----|----------------|----------|
|
|
14
|
+
| Editor at a terminal or a script | FFmpeg on `PATH` (does the encode) | A website, installer, or root/sudo tool |
|
|
15
|
+
|
|
16
|
+
Package version SSOT: `src/VideoSpeed/__init__.py` (`MAJOR_VERSION`, `MINOR_VERSION`, `PATCH_VERSION` → **1.0.9**). `pyproject.toml` copies that string. Console entrypoint: `video-speed`.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- Recursive **MP4** discovery under a chosen folder
|
|
21
|
+
- Interactive **cut** (start/end seconds) with invalid-range re-prompt
|
|
22
|
+
- **Length/speed** change (**20–200%**) with A/V tempo kept in sync
|
|
23
|
+
- Optional **boomerang** (forward then reverse)
|
|
24
|
+
- **USB-safe** intermediate files (staged next to the output) and final publish via `shutil.move`
|
|
25
|
+
- **Text menu** on a terminal (`edit`, `hello`, `about`, `Exit`): columns line up, and a three-row rounded input box sits on the bottom with a status line under it
|
|
26
|
+
- **Product verbs** `help`, `about`, `hello`, `edit`, and `list-mp4`. `help` prints the same usage as `--help`. `about` and `hello` print a page and do not ask for a folder or a file. `edit` on a terminal asks for the folder, then the file. `list-mp4` lists MP4 files and does not encode. `Exit` stays on the menu. `./build.sh` verbs such as `setup` are not `video-speed` verbs
|
|
27
|
+
- **`--help`** / **`--version`**; no arguments in a terminal opens that menu
|
|
28
|
+
- Non-interactive job: `--file`, `--start`, `--end`, optional `--percent` / `--boomerang`, with or without `edit`. Any of those selectors with no product verb skips the menu
|
|
29
|
+
- Fail-closed when **FFmpeg** is missing from `PATH`, when prompts are needed without a terminal, or when `--percent` or `--boomerang` is passed without a job
|
|
30
|
+
|
|
31
|
+
## Quick Installation
|
|
32
|
+
|
|
33
|
+
### Prerequisites (system)
|
|
34
|
+
|
|
35
|
+
- **FFmpeg** on `PATH` (`ffmpeg -version`)
|
|
36
|
+
- A supported **Python** environment (see `requires-python` in `pyproject.toml`)
|
|
37
|
+
|
|
38
|
+
### From PyPI (registry channel)
|
|
39
|
+
|
|
40
|
+
Package name SSOT: **`VideoSpeed`** (`pyproject.toml` `[project].name`). Maintainer verbs are on `./build.sh`: `help`, `version`, `setup`, `clean`, `build`, `test`, `test-install`, `upload`, `git`, `tag`, `release` (`all` is the same chain as `release`). `test` runs `tests/run.sh`. `test-install` reads `[project].name`, removes that pip install when present, and installs this checkout. Empty `./build.sh` prints help and does not upload.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install VideoSpeed
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
If the index is behind a local release, install from a checkout or wheel instead (below). Live index version is shown by the PyPI badge above.
|
|
47
|
+
|
|
48
|
+
### From a git checkout (development)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git clone git@github.com:Wilgat/VideoSpeed.git
|
|
52
|
+
cd VideoSpeed
|
|
53
|
+
pip install -e .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
This installs the console script **`video-speed`** and pulls Python deps (`opencv-python-headless>=5.0.0.93`, `ChronicleLogger>=1.3.1`).
|
|
57
|
+
|
|
58
|
+
### Local pyenv install (testing)
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
./setup.sh
|
|
62
|
+
pyenv shell 3.14
|
|
63
|
+
video-speed --version
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`setup.sh` runs `pyenv shell 3.14` and installs this checkout with pip into that interpreter. Install CPython 3.14 first when it is missing (`pyenv install 3.14`). Run `./setup.sh` again after source changes. In the shell where you want the command:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
pyenv shell 3.14
|
|
70
|
+
video-speed --help
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The console script is installed on the pyenv 3.14 prefix. `./setup.sh` changes that interpreter only. It does not install FFmpeg and it does not use root.
|
|
74
|
+
|
|
75
|
+
### From a local wheel / sdist
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# after: python -m build (or ./build.sh build)
|
|
79
|
+
pip install dist/VideoSpeed-1.0.9-py3-none-any.whl
|
|
80
|
+
# or
|
|
81
|
+
pip install dist/VideoSpeed-1.0.9.tar.gz
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Verify
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
video-speed --version
|
|
88
|
+
# or
|
|
89
|
+
python -m VideoSpeed --version
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Usage
|
|
93
|
+
|
|
94
|
+
Text menu (no arguments, in a terminal):
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
video-speed
|
|
98
|
+
# or
|
|
99
|
+
python -m VideoSpeed
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The screen shows a numbered menu and a rounded input box along the bottom, as wide as the terminal, with one line to type on and a status line under the box. **edit** continues the prompts. **hello** (7) shows `Hello.`. **about** shows the product. **Exit** leaves.
|
|
103
|
+
|
|
104
|
+
The same actions are verbs. `edit` starts at the folder question. `list-mp4` asks for the folder when it is omitted, prints the numbered list, and stops.
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
video-speed help
|
|
108
|
+
video-speed about
|
|
109
|
+
video-speed hello
|
|
110
|
+
video-speed edit
|
|
111
|
+
video-speed edit --folder ./clips
|
|
112
|
+
video-speed edit --file clip.mp4 --start 1 --end 5 --percent 100
|
|
113
|
+
video-speed list-mp4
|
|
114
|
+
video-speed list-mp4 --folder ./clips
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
1. Folder containing MP4s (Enter = current directory)
|
|
118
|
+
2. Choose a video by number
|
|
119
|
+
3. Cut start/end seconds
|
|
120
|
+
4. New length percent (default 100; allowed 20–200)
|
|
121
|
+
5. Optional boomerang (y/n)
|
|
122
|
+
6. Output is written **next to the source** with a descriptive name
|
|
123
|
+
|
|
124
|
+
Non-interactive job (scripts / CI):
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
video-speed --file clip.mp4 --start 1 --end 5 --percent 100
|
|
128
|
+
video-speed --file clip.mp4 --start 1 --end 5 --percent 50 --boomerang
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Other:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
video-speed --help
|
|
135
|
+
video-speed --version
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Without a terminal and without a job, the program exits with an error instead of hanging on prompts. `--percent` or `--boomerang` alone does the same and names `--file`, `--start`, and `--end`. `--folder` checks the directory. It does not pick a file.
|
|
139
|
+
|
|
140
|
+
`--json` keeps that same choice, closes the text menu, and prints one JSON object on standard output. On a terminal with no job flags, the questions are written to the error stream. Progress is not mixed into the object.
|
|
141
|
+
|
|
142
|
+
## Examples
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Install editable, then run interactive session in a folder of clips
|
|
146
|
+
cd /path/to/videos
|
|
147
|
+
video-speed
|
|
148
|
+
|
|
149
|
+
# Check identity without starting prompts
|
|
150
|
+
video-speed --version
|
|
151
|
+
|
|
152
|
+
# One-shot job (no prompts)
|
|
153
|
+
video-speed --file clip.mp4 --start 1.0 --end 5.0 --percent 100
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Example output name shape:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
clip_cut1.0-5.0s_100pct.mp4
|
|
160
|
+
clip_cut1.0-5.0s_50pct_BOOMERANG.mp4
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Platform Compatibility
|
|
164
|
+
|
|
165
|
+
| Platform | Status |
|
|
166
|
+
|----------|--------|
|
|
167
|
+
| Linux | Primary (tested in development) |
|
|
168
|
+
| macOS / Windows | Expected to work when Python, FFmpeg, and OpenCV are available |
|
|
169
|
+
| Filesystems | Local disk and removable/USB volumes supported for output (temps staged beside final path) |
|
|
170
|
+
|
|
171
|
+
## Related Projects
|
|
172
|
+
|
|
173
|
+
- [VideoSpeed on GitHub](https://github.com/Wilgat/VideoSpeed)
|
|
174
|
+
- [VideoSpeed on PyPI](https://pypi.org/project/VideoSpeed/)
|
|
175
|
+
|
|
176
|
+
## Contributing
|
|
177
|
+
|
|
178
|
+
Fork the repository, use a checkout install (`pip install -e .` or `./setup.sh`), and open a pull request. Prefer small, tested changes. Do not strip defensive temp/publish behavior (cross-filesystem `shutil.move`) without an explicit redesign.
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT License — see [`LICENSE.md`](LICENSE.md).
|
|
183
|
+
|
|
184
|
+
## Last Update
|
|
185
|
+
|
|
186
|
+
2026-10-01 — **1.0.9**: a non-TUI, non-JSON run shows `debug mode` when `DEBUG` is already on, and each class logs `instantiated` on the one ChronicleLogger. `def main` stays in `src/VideoSpeed/cli.py`.
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "VideoSpeed"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.9"
|
|
8
8
|
description = "A command-line video editing tool built in Python for cutting, speeding up, and applying a boomerang effect to MP4 videos."
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
11
|
license = {text = "MIT"}
|
|
12
12
|
authors = [
|
|
13
13
|
{name = "Wilgat Wong", email = "wilgat.wong@gmail.com"}
|
|
@@ -15,8 +15,8 @@ authors = [
|
|
|
15
15
|
keywords = ["Video Editing", "Multimeida"]
|
|
16
16
|
|
|
17
17
|
dependencies = [
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"opencv-python-headless>=5.0.0.93",
|
|
19
|
+
"ChronicleLogger>=1.3.1",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
22
|
# Official project links (this is what shows on PyPI!)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Version SSOT (requirement-python-version). Do not import the CLI from here.
|
|
2
|
+
MAJOR_VERSION = 1
|
|
3
|
+
MINOR_VERSION = 0
|
|
4
|
+
PATCH_VERSION = 9
|
|
5
|
+
__version__ = "{0}.{1}.{2}".format(MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION)
|
|
6
|
+
__all__ = ["MAJOR_VERSION", "MINOR_VERSION", "PATCH_VERSION", "__version__"]
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# About page composer.
|
|
3
|
+
# requirement-python-oop — class AboutPage. Line text stays on requirement-python-about.
|
|
4
|
+
# =============================================================================
|
|
5
|
+
from __future__ import print_function, unicode_literals
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
from .check_system import CheckSystem
|
|
10
|
+
from .run_output import log_instantiated
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AboutPage:
|
|
14
|
+
"""Identity lines, install sentence, and the star box. One class, one module.
|
|
15
|
+
|
|
16
|
+
The host check is CheckSystem. This class calls that object.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
check,
|
|
22
|
+
app_name,
|
|
23
|
+
version,
|
|
24
|
+
major,
|
|
25
|
+
minor,
|
|
26
|
+
patch,
|
|
27
|
+
author,
|
|
28
|
+
updated,
|
|
29
|
+
homepage,
|
|
30
|
+
download_url,
|
|
31
|
+
usage,
|
|
32
|
+
console_name,
|
|
33
|
+
logger=None,
|
|
34
|
+
):
|
|
35
|
+
self.logger = logger
|
|
36
|
+
log_instantiated(logger, "AboutPage")
|
|
37
|
+
self.check = check if check is not None else CheckSystem(logger=logger)
|
|
38
|
+
self.app_name = app_name
|
|
39
|
+
self.version = version
|
|
40
|
+
self.major = major
|
|
41
|
+
self.minor = minor
|
|
42
|
+
self.patch = patch
|
|
43
|
+
self.author = author
|
|
44
|
+
self.updated = updated
|
|
45
|
+
self.homepage = homepage
|
|
46
|
+
self.download_url = download_url
|
|
47
|
+
self.usage = usage
|
|
48
|
+
self.console_name = console_name
|
|
49
|
+
|
|
50
|
+
def _is_source_checkout(self, path):
|
|
51
|
+
"""True when path is this package inside a source tree that has pyproject.toml."""
|
|
52
|
+
marker = "{}src{}VideoSpeed{}".format(os.sep, os.sep, os.sep)
|
|
53
|
+
if marker not in path:
|
|
54
|
+
return False
|
|
55
|
+
cursor = os.path.dirname(path)
|
|
56
|
+
for _ in range(8):
|
|
57
|
+
if os.path.isfile(os.path.join(cursor, "pyproject.toml")):
|
|
58
|
+
return True
|
|
59
|
+
parent = os.path.dirname(cursor)
|
|
60
|
+
if parent == cursor:
|
|
61
|
+
return False
|
|
62
|
+
cursor = parent
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
def install_kind(self, path):
|
|
66
|
+
"""
|
|
67
|
+
General Purpose: global, local, or uninstalled for an about location.
|
|
68
|
+
A source checkout is uninstalled. A copy under the home directory is local.
|
|
69
|
+
"""
|
|
70
|
+
path = os.path.realpath(path)
|
|
71
|
+
parts = path.split(os.sep)
|
|
72
|
+
packaged = "site-packages" in parts or "dist-packages" in parts
|
|
73
|
+
if not packaged and self._is_source_checkout(path):
|
|
74
|
+
return "uninstalled"
|
|
75
|
+
home = os.path.realpath(os.path.expanduser("~"))
|
|
76
|
+
in_home = path == home or path.startswith(home + os.sep)
|
|
77
|
+
base = os.path.basename(path)
|
|
78
|
+
if in_home and (packaged or base in (self.console_name, self.app_name)):
|
|
79
|
+
return "local"
|
|
80
|
+
if path.startswith("/usr/") or path.startswith("/opt/") or path.startswith("/bin/"):
|
|
81
|
+
return "global"
|
|
82
|
+
if packaged and not in_home:
|
|
83
|
+
return "global"
|
|
84
|
+
return "uninstalled"
|
|
85
|
+
|
|
86
|
+
def install_sentence(self, kind):
|
|
87
|
+
"""General Purpose: The location sentence inside the about box."""
|
|
88
|
+
if kind == "global":
|
|
89
|
+
return "You are using the GLOBAL INSTALLED version, location:"
|
|
90
|
+
if kind == "local":
|
|
91
|
+
return "You are using the LOCAL INSTALLED version, location:"
|
|
92
|
+
return "You are using an UNINSTALLED version, location:"
|
|
93
|
+
|
|
94
|
+
def about_box_lines(
|
|
95
|
+
self,
|
|
96
|
+
usage=None,
|
|
97
|
+
location=None,
|
|
98
|
+
kind=None,
|
|
99
|
+
homepage=None,
|
|
100
|
+
download_url=None,
|
|
101
|
+
author=None,
|
|
102
|
+
updated=None,
|
|
103
|
+
):
|
|
104
|
+
"""
|
|
105
|
+
General Purpose: Star-bordered about box.
|
|
106
|
+
An empty download URL omits the install line.
|
|
107
|
+
"""
|
|
108
|
+
if usage is None:
|
|
109
|
+
usage = self.usage
|
|
110
|
+
if location is None:
|
|
111
|
+
location = self.check.self_location()
|
|
112
|
+
if kind is None:
|
|
113
|
+
kind = self.install_kind(location)
|
|
114
|
+
if homepage is None:
|
|
115
|
+
homepage = self.homepage
|
|
116
|
+
if download_url is None:
|
|
117
|
+
download_url = self.download_url
|
|
118
|
+
if author is None:
|
|
119
|
+
author = self.author
|
|
120
|
+
if updated is None:
|
|
121
|
+
updated = self.updated
|
|
122
|
+
app = self.install_sentence(kind)
|
|
123
|
+
python_exe = self.check.python_executable_name() or "python3"
|
|
124
|
+
msg1 = "{} ({}.{}.{}) by {} on {}".format(
|
|
125
|
+
self.app_name, self.major, self.minor, self.patch, author, updated
|
|
126
|
+
)
|
|
127
|
+
if download_url == "":
|
|
128
|
+
msg = [
|
|
129
|
+
msg1,
|
|
130
|
+
"",
|
|
131
|
+
app,
|
|
132
|
+
" {}".format(location),
|
|
133
|
+
"",
|
|
134
|
+
"Basic Usage:",
|
|
135
|
+
" {}".format(usage),
|
|
136
|
+
"",
|
|
137
|
+
"Please visit our homepage: ",
|
|
138
|
+
' "{}"'.format(homepage),
|
|
139
|
+
"",
|
|
140
|
+
"",
|
|
141
|
+
"",
|
|
142
|
+
"",
|
|
143
|
+
]
|
|
144
|
+
else:
|
|
145
|
+
msg = [
|
|
146
|
+
msg1,
|
|
147
|
+
"",
|
|
148
|
+
app,
|
|
149
|
+
" {}".format(location),
|
|
150
|
+
"",
|
|
151
|
+
"Basic Usage:",
|
|
152
|
+
" {}".format(usage),
|
|
153
|
+
"",
|
|
154
|
+
"Please visit our homepage: ",
|
|
155
|
+
' "{}"'.format(homepage),
|
|
156
|
+
"",
|
|
157
|
+
"Installation command:",
|
|
158
|
+
" curl -fsSL {} | {}".format(download_url, python_exe),
|
|
159
|
+
"",
|
|
160
|
+
]
|
|
161
|
+
if download_url == "":
|
|
162
|
+
if homepage == "":
|
|
163
|
+
max_line = len(msg) - 3
|
|
164
|
+
else:
|
|
165
|
+
max_line = len(msg) - 2
|
|
166
|
+
else:
|
|
167
|
+
max_line = len(msg) - 1
|
|
168
|
+
if max_line < 1:
|
|
169
|
+
max_line = len(msg)
|
|
170
|
+
max_len = 0
|
|
171
|
+
for index in range(0, max_line):
|
|
172
|
+
if len(msg[index]) > max_len:
|
|
173
|
+
max_len = len(msg[index])
|
|
174
|
+
boxed = []
|
|
175
|
+
for index in range(0, max_line):
|
|
176
|
+
boxed.append(msg[index] + (" " * (max_len - len(msg[index]))))
|
|
177
|
+
star = "*" * (max_len + 4)
|
|
178
|
+
blank = " " * max_len
|
|
179
|
+
lines = [star, "* {} *".format(blank)]
|
|
180
|
+
for row in boxed:
|
|
181
|
+
lines.append("* {} *".format(row))
|
|
182
|
+
lines.append("* {} *".format(blank))
|
|
183
|
+
lines.append(star)
|
|
184
|
+
return lines
|
|
185
|
+
|
|
186
|
+
def framework_about(self):
|
|
187
|
+
"""
|
|
188
|
+
General Purpose: About text for the menu result page.
|
|
189
|
+
requirement-python-about: identity, host check, and the install box.
|
|
190
|
+
"""
|
|
191
|
+
lines = [
|
|
192
|
+
"{} {}".format(self.app_name, self.version),
|
|
193
|
+
"Domain: Cut → speed/length → optional boomerang for MP4",
|
|
194
|
+
"Runtime tools: FFmpeg (encode), OpenCV (duration probe)",
|
|
195
|
+
"Entry points: video-speed, python -m VideoSpeed",
|
|
196
|
+
"",
|
|
197
|
+
]
|
|
198
|
+
lines.extend(self.check.check_system_lines())
|
|
199
|
+
lines.append("")
|
|
200
|
+
lines.extend(self.about_box_lines())
|
|
201
|
+
return "\n".join(lines)
|