VideoJoin 1.0.2__tar.gz → 1.0.5__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.
- videojoin-1.0.5/LICENSE.md +21 -0
- videojoin-1.0.5/PKG-INFO +412 -0
- videojoin-1.0.5/README.md +396 -0
- {videojoin-1.0.2 → videojoin-1.0.5}/pyproject.toml +2 -2
- videojoin-1.0.5/src/VideoJoin/__init__.py +8 -0
- {videojoin-1.0.2 → videojoin-1.0.5}/src/VideoJoin/__main__.py +3 -1
- videojoin-1.0.5/src/VideoJoin/about_page.py +200 -0
- videojoin-1.0.5/src/VideoJoin/check_system.py +521 -0
- videojoin-1.0.5/src/VideoJoin/cli.py +334 -0
- videojoin-1.0.5/src/VideoJoin/join.py +355 -0
- videojoin-1.0.5/src/VideoJoin/menu_language.py +715 -0
- videojoin-1.0.5/src/VideoJoin/menu_model.py +269 -0
- videojoin-1.0.5/src/VideoJoin/menu_painter.py +454 -0
- videojoin-1.0.5/src/VideoJoin/menu_session.py +118 -0
- videojoin-1.0.5/src/VideoJoin/self_management.py +79 -0
- videojoin-1.0.5/src/VideoJoin/system_log.py +92 -0
- videojoin-1.0.5/src/VideoJoin/tui.py +553 -0
- videojoin-1.0.5/src/VideoJoin.egg-info/PKG-INFO +412 -0
- videojoin-1.0.5/src/VideoJoin.egg-info/SOURCES.txt +26 -0
- videojoin-1.0.5/src/VideoJoin.egg-info/requires.txt +1 -0
- videojoin-1.0.5/tests/test_about.py +1067 -0
- videojoin-1.0.5/tests/test_docs.py +74 -0
- videojoin-1.0.5/tests/test_language.py +155 -0
- videojoin-1.0.5/tests/test_tui.py +299 -0
- videojoin-1.0.2/PKG-INFO +0 -193
- videojoin-1.0.2/README.md +0 -179
- videojoin-1.0.2/src/VideoJoin/__init__.py +0 -4
- videojoin-1.0.2/src/VideoJoin/cli.py +0 -114
- videojoin-1.0.2/src/VideoJoin.egg-info/PKG-INFO +0 -193
- videojoin-1.0.2/src/VideoJoin.egg-info/SOURCES.txt +0 -11
- videojoin-1.0.2/src/VideoJoin.egg-info/requires.txt +0 -1
- {videojoin-1.0.2 → videojoin-1.0.5}/setup.cfg +0 -0
- {videojoin-1.0.2 → videojoin-1.0.5}/src/VideoJoin.egg-info/dependency_links.txt +0 -0
- {videojoin-1.0.2 → videojoin-1.0.5}/src/VideoJoin.egg-info/entry_points.txt +0 -0
- {videojoin-1.0.2 → videojoin-1.0.5}/src/VideoJoin.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wilgat Wong <wilgat.wong@gmail.com>
|
|
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.
|
videojoin-1.0.5/PKG-INFO
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: VideoJoin
|
|
3
|
+
Version: 1.0.5
|
|
4
|
+
Summary: VideoJoin is a lightweight command-line tool for concatenating two video files (MP4, MOV, MKV, AVI, M4V) while preserving original audio and video quality using FFmpeg.
|
|
5
|
+
Author-email: Wilgat Wong <wilgat.wong@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Wilgat/VideoJoin
|
|
8
|
+
Project-URL: Repository, https://github.com/Wilgat/VideoJoin
|
|
9
|
+
Project-URL: Issues, https://github.com/Wilgat/VideoJoin/issues
|
|
10
|
+
Keywords: Video Editing,Video,Multimedia
|
|
11
|
+
Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE.md
|
|
14
|
+
Requires-Dist: ChronicleLogger>=1.3.1
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# VideoJoin - Join two local videos with FFmpeg (stream-copy first)
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+

|
|
21
|
+
[-purple.svg)](https://github.com/cloudgen/ciao)
|
|
22
|
+
[](https://github.com/Wilgat/VideoJoin)
|
|
23
|
+
[-blue?style=flat-square)]()
|
|
24
|
+
[](https://pypi.org/project/VideoJoin/)
|
|
25
|
+
|
|
26
|
+
VideoJoin concatenates **two** video files from the current directory using **FFmpeg**. It prefers **stream copy** (no re-encode when possible) and **fail-closed re-encode fallback** when copy fails. Intermediate files are staged next to the output path when possible and published with **`shutil.move`** for multi-mount safety (for example USB).
|
|
27
|
+
|
|
28
|
+
On a terminal, starting it with no arguments opens a text menu. You pick **join** when you want to choose the two files. The menu does not start that job by itself, and it does not call pip.
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- Text menu on a terminal: **join**, **system-log**, **language**, **self-management**, and **Exit**
|
|
33
|
+
- The first row shows the current folder and a local clock (`HH:MM:SS`) when the row has room
|
|
34
|
+
- Numbered rows line up. A rounded input box sits along the bottom, as wide as the terminal, with the name and version on the status line under the box
|
|
35
|
+
- **system-log** (3) opens view-log, clear-log, and log-folder. **language** (4) picks the menu language. **self-management** (8) opens version, about, and the pip lifecycle rows
|
|
36
|
+
- Interactive pick of first and second video (same file cannot be chosen twice)
|
|
37
|
+
- Discovers `.mp4`, `.mov`, `.mkv`, `.avi`, `.m4v` in the **current working directory** (sorted case-insensitively)
|
|
38
|
+
- Stream-copy join first (`ffmpeg` concat demuxer); re-encode fallback (`libx264` CRF 18, AAC 192k)
|
|
39
|
+
- Fail-closed: no success message if both paths fail
|
|
40
|
+
- Unique temp list + media intermediates; cleanup on success and failure
|
|
41
|
+
- Publish intermediates with `shutil.move` (same FS rename; cross-mount copy+delete)
|
|
42
|
+
- FFmpeg on `PATH` checked before joining
|
|
43
|
+
- Console script `video-join` and module entry `python -m VideoJoin`
|
|
44
|
+
- Typed verbs: `help`, `version`, `about`, `hello`, `join`, `list-videos`, `self-install`, `version-check`, `self-update`, `self-uninstall`
|
|
45
|
+
|
|
46
|
+
## Advantages
|
|
47
|
+
|
|
48
|
+
1. **Dual-mode interface.** The menu asks for two indexes and an output name. A typed `join` asks the same questions. The person does not write an FFmpeg concat graph. The program tries stream copy, then re-encodes if copy fails. There is no length percent, no boomerang, and no `--json`. With no arguments on a terminal, the text menu opens. With no terminal and no verb, the program prints help and returns 0. Direct `join` with no terminal exits non-zero.
|
|
49
|
+
2. **Built-in languages.** Row **4** lists thirteen languages: English, Simplified Chinese, Traditional Chinese, Spanish, Arabic, French, Portuguese, Russian, German, Japanese, Korean, Dutch, and Greek. The choice is saved for the next run.
|
|
50
|
+
3. **USB-safe staging.** Intermediate files are written beside the output when that folder can be written, including on a removable drive. The finished file is published with `shutil.move`. When that folder cannot be written, the stage uses the system temporary directory.
|
|
51
|
+
4. **Lifecycle and diagnostics.** `version-check`, `self-update`, `self-install`, and `self-uninstall` are menu rows **84**–**87** and typed verbs. `self-uninstall` on the command line needs `--force`. They call pip and do not use root. **system-log** (**3**) views a log, clears a log, and shows the log folder. **about** (**83**) stays in English.
|
|
52
|
+
|
|
53
|
+
| Feature | Raw FFmpeg CLI | Typical Python wrappers (moviepy) | VideoJoin |
|
|
54
|
+
|---------|----------------|-----------------------------------|-----------|
|
|
55
|
+
| Learning curve | A concat graph the person writes | A Python script | Text menu, or two file numbers and an output name |
|
|
56
|
+
| Non-interactive | Native command line | A custom script | Typed verbs. No terminal and no verb: help, return 0. Direct `join` with no terminal: non-zero. No `--json` |
|
|
57
|
+
| Menu languages | None | None | Thirteen, kept for the next run |
|
|
58
|
+
| Temporary files | You choose the paths | Often the system temporary directory | Beside the output when that folder can be written. Otherwise the system temporary directory. Publish with `shutil.move` |
|
|
59
|
+
| Self-management | The system package manager | pip from outside the tool | Built-in version-check, self-update, self-install, and self-uninstall |
|
|
60
|
+
| Diagnostics and logs | The encode stream | A logging setup in the script | system-log (row 3) and about (83), in English |
|
|
61
|
+
|
|
62
|
+
## Quick Installation
|
|
63
|
+
|
|
64
|
+
**System requirement:** [FFmpeg](https://ffmpeg.org/download.html) must be installed and available as `ffmpeg` on your `PATH`. The package does **not** install FFmpeg via pip.
|
|
65
|
+
|
|
66
|
+
**Python dependency:** `ChronicleLogger>=1.3.1` (required). Pip installs it with VideoJoin.
|
|
67
|
+
|
|
68
|
+
### PyPI (registry)
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install VideoJoin
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This installs the console entry **`video-join`** and the package **`VideoJoin`**. Packaging name SSOT is `pyproject.toml` `[project].name` = `VideoJoin`. This tree is **1.0.5**. A registry probe on 2026-08-19 reported **1.0.3**. The PyPI badge above shows the live index.
|
|
75
|
+
|
|
76
|
+
### Local install (checkout)
|
|
77
|
+
|
|
78
|
+
From a checkout (editable / unreleased work):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
git clone https://github.com/Wilgat/VideoJoin.git
|
|
82
|
+
cd VideoJoin
|
|
83
|
+
python3 -m venv venv
|
|
84
|
+
source venv/bin/activate # Windows: venv\Scripts\activate
|
|
85
|
+
pip install -e .
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Main menu
|
|
89
|
+
|
|
90
|
+
After install, on a terminal, `video-join` with no arguments prints this screen. The verb is bold and the note after the colon is italic. `9` leaves. `0` on a submenu goes back. After a command finishes, the main menu is shown again. A number that is not on the list prints an error and lets you choose again.
|
|
91
|
+
|
|
92
|
+
Picture of the English menu. Current directory `/tmp/clips`. The rows are the text the menu paints. The frame is the 80-column box.
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
$ video-join
|
|
96
|
+
Path: /tmp/clips 12:06:45
|
|
97
|
+
|
|
98
|
+
1. **join** : *pick two videos in this folder and concatenate*
|
|
99
|
+
3. **system-log** : *view, clear, and the log folder*
|
|
100
|
+
4. **language** : *display language for this menu*
|
|
101
|
+
8. **self-management**: *version, about, and pip lifecycle*
|
|
102
|
+
9. **Exit** : *leave*
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
╭─────────────────────────────────────────────────────────────────────────────╮
|
|
108
|
+
│ > │
|
|
109
|
+
╰─────────────────────────────────────────────────────────────────────────────╯
|
|
110
|
+
VideoJoin 1.0.4 │ main menu │ Up/Down • Enter
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**system-log** (3):
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
Path: /tmp/clips 12:06:45
|
|
117
|
+
|
|
118
|
+
31. **view-log** : *list a log file and show it*
|
|
119
|
+
32. **clear-log** : *empty one log file*
|
|
120
|
+
33. **log-folder**: *show the log folder*
|
|
121
|
+
0. **Back** : *return to the main menu*
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
╭─────────────────────────────────────────────────────────────────────────────╮
|
|
127
|
+
│ > │
|
|
128
|
+
╰─────────────────────────────────────────────────────────────────────────────╯
|
|
129
|
+
VideoJoin 1.0.4 │ system-log │ Up/Down • Enter
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**language** (4). The short on each language row is that language’s own name. Numbers 40 and 54–59 are not printed. `0` goes back and does not save.
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
Path: /tmp/clips 12:06:46
|
|
136
|
+
|
|
137
|
+
41. **English** : *use English for this menu*
|
|
138
|
+
42. **简体中文** : *use 简体中文 for this menu*
|
|
139
|
+
43. **繁體中文** : *use 繁體中文 for this menu*
|
|
140
|
+
44. **Español** : *use Español for this menu*
|
|
141
|
+
45. **العربية** : *use العربية for this menu*
|
|
142
|
+
46. **Français** : *use Français for this menu*
|
|
143
|
+
47. **Português** : *use Português for this menu*
|
|
144
|
+
48. **Русский** : *use Русский for this menu*
|
|
145
|
+
49. **Deutsch** : *use Deutsch for this menu*
|
|
146
|
+
50. **日本語** : *use 日本語 for this menu*
|
|
147
|
+
51. **한국어** : *use 한국어 for this menu*
|
|
148
|
+
52. **Nederlands**: *use Nederlands for this menu*
|
|
149
|
+
53. **Ελληνικά** : *use Ελληνικά for this menu*
|
|
150
|
+
0. **Back** : *return to the main menu*
|
|
151
|
+
|
|
152
|
+
╭─────────────────────────────────────────────────────────────────────────────╮
|
|
153
|
+
│ > │
|
|
154
|
+
╰─────────────────────────────────────────────────────────────────────────────╯
|
|
155
|
+
VideoJoin 1.0.4 │ language │ Up/Down • Enter
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**self-management** (8):
|
|
159
|
+
|
|
160
|
+
```text
|
|
161
|
+
Path: /tmp/clips 14:05:09
|
|
162
|
+
|
|
163
|
+
82. **version** : *show the installed version*
|
|
164
|
+
83. **about** : *version, FFmpeg, and this computer*
|
|
165
|
+
84. **version-check** : *compare this install with pip*
|
|
166
|
+
85. **self-update** : *upgrade this package with pip*
|
|
167
|
+
86. **self-uninstall**: *remove this package with pip*
|
|
168
|
+
87. **self-install** : *install this package with pip*
|
|
169
|
+
0. **Back** : *return to the main menu*
|
|
170
|
+
|
|
171
|
+
╭─────────────────────────────────────────────────────────────────────────────╮
|
|
172
|
+
│ > │
|
|
173
|
+
╰─────────────────────────────────────────────────────────────────────────────╯
|
|
174
|
+
VideoJoin 1.0.5 │ self-management │ Up/Down • Enter
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Choose a number, or type the command name in the box. The block caret appears in that box while it is focused.
|
|
178
|
+
|
|
179
|
+
## Usage
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
video-join
|
|
183
|
+
# or
|
|
184
|
+
python -m VideoJoin
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
On a terminal, that opens the main menu above. It does not ask for videos yet, and it does not call pip. With no terminal, the same command prints help and returns 0.
|
|
188
|
+
|
|
189
|
+
**join** (1), or `video-join join` on a terminal, stays on the screen and asks in the bottom box:
|
|
190
|
+
|
|
191
|
+
1. First video number
|
|
192
|
+
2. Second video number from the remaining list
|
|
193
|
+
3. Output name (Enter uses `{stem1} + {stem2}.mp4`)
|
|
194
|
+
|
|
195
|
+
Esc returns to the main menu and does not join. Fewer than two eligible videos fails closed: FFmpeg does not run. `video-join join` with no terminal exits non-zero and tells you to use a terminal.
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
video-join help
|
|
199
|
+
video-join version
|
|
200
|
+
video-join about
|
|
201
|
+
video-join hello
|
|
202
|
+
video-join list-videos
|
|
203
|
+
video-join version-check
|
|
204
|
+
video-join self-update
|
|
205
|
+
video-join self-install
|
|
206
|
+
video-join self-uninstall --force
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`version` prints `VideoJoin 1.0.5` and does not call pip. `about` shows one English page: the product identity, a host check of this computer, and a star box. It does not install FFmpeg and it does not call pip. `hello` prints a one-line greeting and does not draw the menu. `list-videos` lists eligible names and does not join. `help` prints usage.
|
|
210
|
+
|
|
211
|
+
`version-check` runs `python -m pip index versions VideoJoin`. `self-update` runs `python -m pip install --upgrade VideoJoin`. `self-install` runs `python -m pip install VideoJoin`. `self-uninstall` runs `python -m pip uninstall -y VideoJoin` and needs `--force` on the command line. Those pip verbs do not use sudo. Empty arguments do not install or update.
|
|
212
|
+
|
|
213
|
+
Exit codes: non-zero if join is started with no terminal, fewer than two videos, FFmpeg is missing, or the join fails. Menu **Exit** (9) returns 0.
|
|
214
|
+
|
|
215
|
+
Import for scripts (thin entry):
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from VideoJoin import main
|
|
219
|
+
# interactive session; a terminal opens the text menu
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Screenshots
|
|
223
|
+
|
|
224
|
+
Each heading is the file name. The paragraph is what that picture shows: the words on the screen, the characters in the input box, or the scene. Package **1.0.5**. Menu pictures captured before the about page still show **1.0.4** where the paragraph says so. The package index can fetch a picture only after that file is on the public `main` branch.
|
|
225
|
+
|
|
226
|
+
### `language-menu.png`
|
|
227
|
+
|
|
228
|
+
Row **4** has opened the language list. **41 English** is highlighted, with the note "use English for this menu." The other rows are **42 简体中文**, **43 繁體中文**, **44 Español**, **45 العربية**, **46 Français**, **47 Português**, **48 Русский**, **49 Deutsch**, **50 日本語**, **51 한국어**, **52 Nederlands**, and **53 Ελληνικά**. Each note says to use that language for this menu. **0 Back** says "return to the main menu." The path label is `Path`. The clock is on the right of that row. The status line says `language`. VideoJoin 1.0.4.
|
|
229
|
+
|
|
230
|
+

|
|
231
|
+
|
|
232
|
+
### `main-menu-en.png`
|
|
233
|
+
|
|
234
|
+
English main menu. There is no saved-language line above the box. The path label is `Path`. The clock is on the right of that row. **1 join** is highlighted: "pick two videos in this folder and concatenate." Then **3 system-log** "view, clear, and the log folder," **4 language** "display language for this menu," **8 self-management** "version, about, and pip lifecycle," and **9 Exit** "leave." The status line says `main menu`.
|
|
235
|
+
|
|
236
|
+

|
|
237
|
+
|
|
238
|
+
### `main-menu-zh-hans.png`
|
|
239
|
+
|
|
240
|
+
Simplified Chinese main menu. The line above the box says `菜单语言是简体中文`. The path label is `路径`. The clock is on the right of that row. **1 join** is highlighted and stays `join`. **3** is `系统日志`, **4** is `语言`, **8** is `自我管理`, and **9** is `离开`. The status line says `主菜单`.
|
|
241
|
+
|
|
242
|
+

|
|
243
|
+
|
|
244
|
+
### `main-menu-zh-hant.png`
|
|
245
|
+
|
|
246
|
+
Traditional Chinese main menu. The line above the box says `選單語言是繁體中文`. The path label is `路徑`. The clock is on the right of that row. **1 join** is highlighted and stays `join`. **3** is `系統日誌`, **4** is `語言`, **8** is `自我管理`, and **9** is `離開`. The status line says `主選單`.
|
|
247
|
+
|
|
248
|
+

|
|
249
|
+
|
|
250
|
+
### `main-menu-es.png`
|
|
251
|
+
|
|
252
|
+
Spanish main menu. The line above the box says `El idioma del menú es español`. The path label is `Ruta`. The clock is on the right of that row. **1 join** is highlighted and stays `join`. **3** is `registro`, **4** is `idioma`, **8** is `autogestión`, and **9** is `Salir`. The status line says `menú principal`.
|
|
253
|
+
|
|
254
|
+

|
|
255
|
+
|
|
256
|
+
### `main-menu-ar.png`
|
|
257
|
+
|
|
258
|
+
Arabic main menu. The line above the box says `لغة القائمة هي العربية`. The path label is `المسار`. The clock is on the right of that row. The numbers stay on the left. Arabic words on each row are shaped and read right to left. **1 join** is highlighted and stays `join`. **3** is `سجل النظام`, **4** is `لغة`, **8** is `إدارة ذاتية`, and **9** is `خروج`. The status line says `القائمة الرئيسية`.
|
|
259
|
+
|
|
260
|
+

|
|
261
|
+
|
|
262
|
+
### `main-menu-fr.png`
|
|
263
|
+
|
|
264
|
+
French main menu. The line above the box says `La langue du menu est le français`. The path label is `Chemin`. The clock is on the right of that row. **1 join** is highlighted and stays `join`. **3** is `journal`, **4** is `langue`, **8** is `autogestion`, and **9** is `Quitter`. The status line says `menu principal`.
|
|
265
|
+
|
|
266
|
+

|
|
267
|
+
|
|
268
|
+
### `main-menu-pt.png`
|
|
269
|
+
|
|
270
|
+
Portuguese main menu. The line above the box says `O idioma do menu é português`. The path label is `Caminho`. The clock is on the right of that row. **1 join** is highlighted and stays `join`. **3** is `registo`, **4** is `idioma`, **8** is `autogestão`, and **9** is `Sair`. The status line says `menu principal`.
|
|
271
|
+
|
|
272
|
+

|
|
273
|
+
|
|
274
|
+
### `main-menu-ru.png`
|
|
275
|
+
|
|
276
|
+
Russian main menu. The line above the box says `Язык меню — русский`. The path label is `Путь`. The clock is on the right of that row. **1 join** is highlighted and stays `join`. **3** is `системный журнал`, **4** is `язык`, **8** is `самоуправление`, and **9** is `Выход`. The status line says `главное меню`.
|
|
277
|
+
|
|
278
|
+

|
|
279
|
+
|
|
280
|
+
### `main-menu-de.png`
|
|
281
|
+
|
|
282
|
+
German main menu. The line above the box says `Die Menüsprache ist Deutsch`. The path label is `Pfad`. The clock is on the right of that row. **1 join** is highlighted and stays `join`. **3** is `Systemprotokoll`, **4** is `Sprache`, **8** is `Selbstverwaltung`, and **9** is `Beenden`. The status line says `Hauptmenü`.
|
|
283
|
+
|
|
284
|
+

|
|
285
|
+
|
|
286
|
+
### `main-menu-ja.png`
|
|
287
|
+
|
|
288
|
+
Japanese main menu. The line above the box says `メニューの言語は日本語`. The path label is `パス`. The clock is on the right of that row. **1 join** is highlighted and stays `join`. **3** is `システムログ`, **4** is `言語`, **8** is `自己管理`, and **9** is `終了`. The status line says `メインメニュー`.
|
|
289
|
+
|
|
290
|
+

|
|
291
|
+
|
|
292
|
+
### `main-menu-ko.png`
|
|
293
|
+
|
|
294
|
+
Korean main menu. The line above the box says `메뉴 언어는 한국어`. The path label is `경로`. The clock is on the right of that row. **1 join** is highlighted and stays `join`. **3** is `시스템 로그`, **4** is `언어`, **8** is `자기관리`, and **9** is `종료`. The status line says `주 메뉴`.
|
|
295
|
+
|
|
296
|
+

|
|
297
|
+
|
|
298
|
+
### `main-menu-nl.png`
|
|
299
|
+
|
|
300
|
+
Dutch main menu. The line above the box says `De menutaal is Nederlands`. The path label is `Pad`. The clock is on the right of that row. **1 join** is highlighted and stays `join`. **3** is `systeemlog`, **4** is `taal`, **8** is `zelfbeheer`, and **9** is `Afsluiten`. The status line says `hoofdmenu`.
|
|
301
|
+
|
|
302
|
+

|
|
303
|
+
|
|
304
|
+
### `main-menu-el.png`
|
|
305
|
+
|
|
306
|
+
Greek main menu. The line above the box says `Η γλώσσα του μενού είναι ελληνικά`. The path label is `Διαδρομή`. The clock is on the right of that row. **1 join** is highlighted and stays `join`. **3** is `αρχείο καταγραφής`, **4** is `γλώσσα`, **8** is `αυτοδιαχείριση`, and **9** is `Έξοδος`. The status line says `κύριο μενού`.
|
|
307
|
+
|
|
308
|
+

|
|
309
|
+
|
|
310
|
+
### `join-first.png`
|
|
311
|
+
|
|
312
|
+
Join, first question. The title is `VideoJoin (1.0.4) — join`. The page says "Join two videos in this folder." and "Esc returns to the menu." The list is `1. clip-a.mp4` and `2. clip-b.mp4`. The prompt is "Choose FIRST video →". The input box contains `1` and the block caret. The status line says `join`. There is no clock on this page.
|
|
313
|
+
|
|
314
|
+

|
|
315
|
+
|
|
316
|
+
### `join-second.png`
|
|
317
|
+
|
|
318
|
+
Join, second question. The title is `VideoJoin (1.0.4) — join`. The remaining list is `1. clip-b.mp4`. The prompt is "Choose SECOND video →". The input box contains `1` and the block caret. The status line says `join`.
|
|
319
|
+
|
|
320
|
+

|
|
321
|
+
|
|
322
|
+
### `join-output.png`
|
|
323
|
+
|
|
324
|
+
Join, output name. The page says "Joining:", then `clip-a.mp4`, then `+ clip-b.mp4`, then "Output filename [clip-a + clip-b.mp4]:". The input box is empty except for the block caret, so the bracketed default stands. The status line says `join`.
|
|
325
|
+
|
|
326
|
+

|
|
327
|
+
|
|
328
|
+
### `join-done.png`
|
|
329
|
+
|
|
330
|
+
The join has finished by stream copy. The page says "Joining with perfect audio sync:", lists `clip-a.mp4` and `clip-b.mp4`, then "→ clip-a + clip-b.mp4", then "Staging dir → .", then "Running ffmpeg (stream copy – no quality loss)…", then "SUCCESS! Perfectly joined with original sound → clip-a + clip-b.mp4". The footer says "Press a key to return to the main menu." The input box is empty. The status line says `join`.
|
|
331
|
+
|
|
332
|
+

|
|
333
|
+
|
|
334
|
+
### `self-management.png`
|
|
335
|
+
|
|
336
|
+
Row **8** has opened self-management. **82 version** is highlighted: "show the installed version." Then **83 about** "version, FFmpeg, and this computer", **84 version-check** "compare this install with pip", **85 self-update** "upgrade this package with pip", **86 self-uninstall** "remove this package with pip", **87 self-install** "install this package with pip", and **0 Back** "return to the main menu." The path label is `Path`. The path is `/tmp/clips`. The clock is `14:05:09` on the right of that row. The status line says `VideoJoin 1.0.5` and `self-management`.
|
|
337
|
+
|
|
338
|
+

|
|
339
|
+
|
|
340
|
+
### `tui-about.png`
|
|
341
|
+
|
|
342
|
+
**about** (83) on the result page. The title is `VideoJoin (1.0.5) — result`. The page prints `VideoJoin 1.0.5`, `Domain: Concatenate two local videos with FFmpeg (copy, then fallback)`, `Runtime tools: FFmpeg (copy, then re-encode)`, and `Entry points: video-join, python -m VideoJoin`. The host check is stamped `2026-10-04 21:14:59.669782` and headed `[CHECK SYSTEM]:`. Visible lines include Python 3.12.11, C Library GCC 13.3.0, Ubuntu 24.04.5 LTS, amd64, the current user, the shell, the Python executable, python2 location, python3 location, conda location, pyenv location, `Inside docker container: False`, and `Cython String: cpython-312-x86_64-linux-gnu`. The footer says `Up/Down scrolls this page.` and `Press a key to return to the main menu.` There is no input box and no clock. The page stays in English.
|
|
343
|
+
|
|
344
|
+

|
|
345
|
+
|
|
346
|
+
### `system-log.png`
|
|
347
|
+
|
|
348
|
+
Row **3** has opened system-log. **31 view-log** is highlighted: "list a log file and show it." Then **32 clear-log** "empty one log file", **33 log-folder** "show the log folder", and **0 Back** "return to the main menu." The path label is `Path`. The clock is on the right of that row. The status line says `system-log`.
|
|
349
|
+
|
|
350
|
+

|
|
351
|
+
|
|
352
|
+
### `video.png`
|
|
353
|
+
|
|
354
|
+
A still picture, not a text-menu capture. A woman with long brown hair, in a grey shirt with a small blue mark, sits at a round wooden table and holds a white cup. An open book with a worn cover and a metal clasp lies on the table. Behind her are a beige sofa, a wide window onto trees, a potted plant, and a wooden floor in daylight.
|
|
355
|
+
|
|
356
|
+

|
|
357
|
+
|
|
358
|
+
## Examples
|
|
359
|
+
|
|
360
|
+
```bash
|
|
361
|
+
cd /path/to/folder/with/clips
|
|
362
|
+
video-join
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
video-join version
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
`version` prints `VideoJoin 1.0.5` and does not call pip. A run that is not the text menu can also print ChronicleLogger status lines above that. The text menu keeps those lines off the screen.
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
video-join join
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
The menu pictures are in Screenshots. Join still prefers stream copy, then re-encode, and publishes the result with `shutil.move`.
|
|
376
|
+
|
|
377
|
+
## Platform Compatibility
|
|
378
|
+
|
|
379
|
+
| Platform | Status |
|
|
380
|
+
|----------|--------|
|
|
381
|
+
| Linux | Primary; tested development path |
|
|
382
|
+
| macOS | Supported when Python + FFmpeg on PATH |
|
|
383
|
+
| Windows | Supported when Python + FFmpeg on PATH (venv activate differs) |
|
|
384
|
+
| Architectures | Any with CPython + FFmpeg binary |
|
|
385
|
+
|
|
386
|
+
The text menu needs a terminal. With no terminal and no verb, the program prints help and returns 0. It does not wait.
|
|
387
|
+
|
|
388
|
+
## Related Projects
|
|
389
|
+
|
|
390
|
+
- [VideoJoin on GitHub](https://github.com/Wilgat/VideoJoin) — this program’s source
|
|
391
|
+
- [VideoJoin on PyPI](https://pypi.org/project/VideoJoin/) — this program on PyPI
|
|
392
|
+
- [AnimeDlp](https://github.com/Wilgat/AnimeDlp) — command-line downloader for anime video sites
|
|
393
|
+
- [ChronicleLogger](https://github.com/Wilgat/ChronicleLogger) — status logger this program depends on (`ChronicleLogger>=1.3.1`)
|
|
394
|
+
- [VideoSpeed](https://github.com/Wilgat/VideoSpeed) — cuts, changes speed, and boomerangs a clip. That program is not this one
|
|
395
|
+
- [CIAO](https://github.com/cloudgen/ciao) — Caution, Intentional, Anti-fragile, Over-engineered
|
|
396
|
+
- [CIAO-Lite](https://github.com/cloudgen/ciao-lite) — short agent contract
|
|
397
|
+
- [safe-rm](https://github.com/cloudgen/safe-rm) — guarded `rm`
|
|
398
|
+
|
|
399
|
+
## Contributing
|
|
400
|
+
|
|
401
|
+
1. Keep product law under `docs/requirements/` in sync when behavior changes.
|
|
402
|
+
2. Prefer small, CIAO-safe changes; do not remove Protection Zones in the join publish path (staging / `shutil.move`) without explicit design.
|
|
403
|
+
3. Version dual SSOT: bump **`pyproject.toml`** and **`src/VideoJoin/__init__.__version__`** together.
|
|
404
|
+
4. Open issues and pull requests on GitHub.
|
|
405
|
+
|
|
406
|
+
## License
|
|
407
|
+
|
|
408
|
+
MIT — see [`LICENSE.md`](./LICENSE.md). Also declared in `pyproject.toml`.
|
|
409
|
+
|
|
410
|
+
## Last Update
|
|
411
|
+
|
|
412
|
+
2026-10-04 — **1.0.5**: the about page names this build, this computer, and where the program is running from. Self-management row 83 says "version, FFmpeg, and this computer." Those two pictures were recaptured. The other menu pictures still show **1.0.4**. Version badge matches `pyproject.toml` and `__version__`. `ChronicleLogger>=1.3.1` is required.
|