devbits 1.3.3__tar.gz → 1.4.1__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.
- {devbits-1.3.3 → devbits-1.4.1}/PKG-INFO +72 -7
- {devbits-1.3.3 → devbits-1.4.1}/README.md +71 -6
- {devbits-1.3.3 → devbits-1.4.1}/devbits/__init__.py +1 -1
- {devbits-1.3.3 → devbits-1.4.1}/devbits/cli.py +153 -11
- {devbits-1.3.3 → devbits-1.4.1}/devbits/gui.py +313 -13
- devbits-1.4.1/devbits/network.py +967 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits/scripts.py +9 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits.egg-info/PKG-INFO +72 -7
- {devbits-1.3.3 → devbits-1.4.1}/devbits.egg-info/SOURCES.txt +1 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits.egg-info/entry_points.txt +2 -0
- {devbits-1.3.3 → devbits-1.4.1}/pyproject.toml +4 -2
- {devbits-1.3.3 → devbits-1.4.1}/tests/test_gui_cli.py +45 -9
- devbits-1.4.1/tests/test_netsurvey.py +346 -0
- devbits-1.3.3/devbits/network.py +0 -311
- {devbits-1.3.3 → devbits-1.4.1}/LICENSE +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits/cache.py +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits/image.py +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits/media.py +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits/project.py +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits/tui.py +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits/utils.py +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits/wifi.py +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits.egg-info/dependency_links.txt +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits.egg-info/requires.txt +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/devbits.egg-info/top_level.txt +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/setup.cfg +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/tests/test_cli.py +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/tests/test_tui.py +0 -0
- {devbits-1.3.3 → devbits-1.4.1}/tests/test_wifi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devbits
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.1
|
|
4
4
|
Summary: A lightweight CLI toolkit for daily development utilities.
|
|
5
5
|
Author: Bruce Chuang
|
|
6
6
|
License-Expression: MIT
|
|
@@ -42,8 +42,8 @@ devbits <command> [options]
|
|
|
42
42
|
Use `--help` on any command for detailed usage and parameter descriptions:
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
devbits
|
|
46
|
-
|
|
45
|
+
devbits editvideo --help
|
|
46
|
+
editvideo --help
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
## Commands
|
|
@@ -52,7 +52,7 @@ clipvideo --help
|
|
|
52
52
|
|
|
53
53
|
| Command | Description |
|
|
54
54
|
|---------|-------------|
|
|
55
|
-
| `
|
|
55
|
+
| `editvideo` | Trim a video by time (seconds) or frame range. Includes `--gui` for browser-based editing. (Formerly `clipvideo`, which still works but prints a rename notice.) |
|
|
56
56
|
| `video2images` | Extract frames from a video. |
|
|
57
57
|
| `video2gif` | Convert a video (or a portion) to animated GIF. |
|
|
58
58
|
| `images2video` | Assemble an image sequence into an MP4 video. |
|
|
@@ -85,16 +85,17 @@ clipvideo --help
|
|
|
85
85
|
| Command | Description |
|
|
86
86
|
|---------|-------------|
|
|
87
87
|
| `netscan` | List devices connected to your local network (Wi-Fi / router) with their IP, MAC, and hostname. `--lookup` adds the manufacturer. |
|
|
88
|
+
| `netsurvey` | Map **every network segment** visible on the current link — your own interfaces, foreign subnets on the same switch, the addresses in use, and the conflicts between them. Built for shared networks (trade shows, offices). |
|
|
88
89
|
| `wifi` | Manage Wi-Fi: `list`, `connect` (arrow-key picker + hidden password prompt), `on`, `off`, `forget`. Linux and Windows fully; macOS without `list`. |
|
|
89
90
|
|
|
90
91
|
## Examples
|
|
91
92
|
|
|
92
93
|
```bash
|
|
93
94
|
# Trim video from 5s to 20s
|
|
94
|
-
|
|
95
|
+
editvideo movie.mp4 --start 5.0 --end 20.0
|
|
95
96
|
|
|
96
97
|
# Open interactive clip editor in the browser
|
|
97
|
-
|
|
98
|
+
editvideo movie.mp4 --gui
|
|
98
99
|
|
|
99
100
|
# Convert video to GIF (3.5s–10s at 15 fps)
|
|
100
101
|
video2gif movie.mp4 --start 3.5 --end 10.0 --fps 15
|
|
@@ -124,6 +125,18 @@ netscan --lookup
|
|
|
124
125
|
# Scan a specific subnet, faster, without hostname lookups
|
|
125
126
|
netscan --network 192.168.1.0/24 --timeout 0.5 --no-resolve
|
|
126
127
|
|
|
128
|
+
# Map every network segment and address around you (passive, ~2s)
|
|
129
|
+
netsurvey
|
|
130
|
+
|
|
131
|
+
# Segments and conflicts only, no address lists
|
|
132
|
+
netsurvey --summary
|
|
133
|
+
|
|
134
|
+
# Also ping-sweep your ranges and the discovered ones
|
|
135
|
+
netsurvey --sweep
|
|
136
|
+
|
|
137
|
+
# Check specific ranges before assigning them at a venue
|
|
138
|
+
netsurvey --include 192.168.1.0/24 --include 10.0.0.0/24
|
|
139
|
+
|
|
127
140
|
# Show the Wi-Fi networks in range (Linux / Windows)
|
|
128
141
|
wifi list
|
|
129
142
|
|
|
@@ -146,6 +159,58 @@ wifi forget OldCafe
|
|
|
146
159
|
> laptops that use a randomized/private MAC show up as `(private)` and can't be
|
|
147
160
|
> attributed to a vendor.
|
|
148
161
|
|
|
162
|
+
### Surveying a shared network
|
|
163
|
+
|
|
164
|
+
`netscan` answers "who is on **my** subnet?". `netsurvey` answers "what is on
|
|
165
|
+
this **wire**?" — the question that matters in an exhibition hall, an office, or
|
|
166
|
+
a co-working space where other people's routers hand out their own ranges:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
INTERFACES
|
|
170
|
+
en0 192.168.0.122/24 gateway 192.168.0.1
|
|
171
|
+
utun6 10.2.244.44/32
|
|
172
|
+
|
|
173
|
+
SEGMENTS (3 total, 2 yours, 9 address(es) in use)
|
|
174
|
+
192.168.0.0/24 local (en0) swept 6 host(s)
|
|
175
|
+
192.168.0.1 06:f2:67:75:4d:e2 gateway,arp,ping gateway / router
|
|
176
|
+
192.168.0.122 4e:e5:41:93:7c:1f self,arp,mdns this device
|
|
177
|
+
...
|
|
178
|
+
10.77.0.0/24 foreign passive 2 host(s)
|
|
179
|
+
10.77.0.9 de:ad:be:ef:00:01 ssdp,arp
|
|
180
|
+
|
|
181
|
+
CONFLICTS (2)
|
|
182
|
+
! [duplicate-ip] 192.168.0.50 answers from 2 MAC addresses (…) — two devices claim the same address
|
|
183
|
+
- [shared-l2] 1 foreign segment(s) share this link: 10.77.0.0/24
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Three independent sources are combined, so segments you have no address in still
|
|
187
|
+
show up:
|
|
188
|
+
|
|
189
|
+
- **every local interface** with its real netmask (`ip` / `ifconfig` / `ipconfig`);
|
|
190
|
+
- the **ARP / neighbour cache**, which lists link neighbours *regardless of
|
|
191
|
+
subnet* — this is what exposes somebody else's range on the same switch;
|
|
192
|
+
- **SSDP and mDNS** answers, sent out of each interface (skip with `--no-passive`).
|
|
193
|
+
|
|
194
|
+
The default pass is passive and takes about two seconds. `--sweep` additionally
|
|
195
|
+
ping-sweeps your ranges and the discovered ones so each address list is complete;
|
|
196
|
+
`--include CIDR` sweeps a specific range you are about to assign. Ranges larger
|
|
197
|
+
than `--max-sweep` (4096 hosts) are narrowed to the /24 around your own address
|
|
198
|
+
or skipped, with a note on stderr.
|
|
199
|
+
|
|
200
|
+
Reported conflicts:
|
|
201
|
+
|
|
202
|
+
| Kind | Meaning |
|
|
203
|
+
|------|---------|
|
|
204
|
+
| `duplicate-ip` | One address answers from two MACs — two devices claim it. |
|
|
205
|
+
| `overlap` | A foreign range, or a second local interface (VPN!), overlaps one of your subnets. |
|
|
206
|
+
| `no-dhcp` | An interface self-assigned a `169.254.x.x` address; no DHCP server answered. |
|
|
207
|
+
| `router-bridge` | One MAC answers for addresses in several segments — a router joining them. |
|
|
208
|
+
| `shared-l2` | Foreign segments are present on your link (informational). |
|
|
209
|
+
|
|
210
|
+
> Survey only networks you are entitled to. `netsurvey` sends ordinary pings and
|
|
211
|
+
> standard discovery multicast — nothing privileged — but probing networks you
|
|
212
|
+
> don't administer may still violate policy or law.
|
|
213
|
+
|
|
149
214
|
### Wi-Fi
|
|
150
215
|
|
|
151
216
|
`wifi connect` lists everything in range — move with ↑/↓, press Enter to join,
|
|
@@ -225,7 +290,7 @@ dependency for one platform, devbits doesn't scan on macOS at all.
|
|
|
225
290
|
When `-o` / `--output` is omitted, the output filename is derived from the input:
|
|
226
291
|
|
|
227
292
|
```
|
|
228
|
-
|
|
293
|
+
editvideo movie.mp4 → movie_clip.mp4
|
|
229
294
|
video2gif movie.mp4 → movie.gif
|
|
230
295
|
resizeimage photo.jpg → photo_resized.jpg
|
|
231
296
|
recolor logo.png → logo_revised.png
|
|
@@ -25,8 +25,8 @@ devbits <command> [options]
|
|
|
25
25
|
Use `--help` on any command for detailed usage and parameter descriptions:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
devbits
|
|
29
|
-
|
|
28
|
+
devbits editvideo --help
|
|
29
|
+
editvideo --help
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
## Commands
|
|
@@ -35,7 +35,7 @@ clipvideo --help
|
|
|
35
35
|
|
|
36
36
|
| Command | Description |
|
|
37
37
|
|---------|-------------|
|
|
38
|
-
| `
|
|
38
|
+
| `editvideo` | Trim a video by time (seconds) or frame range. Includes `--gui` for browser-based editing. (Formerly `clipvideo`, which still works but prints a rename notice.) |
|
|
39
39
|
| `video2images` | Extract frames from a video. |
|
|
40
40
|
| `video2gif` | Convert a video (or a portion) to animated GIF. |
|
|
41
41
|
| `images2video` | Assemble an image sequence into an MP4 video. |
|
|
@@ -68,16 +68,17 @@ clipvideo --help
|
|
|
68
68
|
| Command | Description |
|
|
69
69
|
|---------|-------------|
|
|
70
70
|
| `netscan` | List devices connected to your local network (Wi-Fi / router) with their IP, MAC, and hostname. `--lookup` adds the manufacturer. |
|
|
71
|
+
| `netsurvey` | Map **every network segment** visible on the current link — your own interfaces, foreign subnets on the same switch, the addresses in use, and the conflicts between them. Built for shared networks (trade shows, offices). |
|
|
71
72
|
| `wifi` | Manage Wi-Fi: `list`, `connect` (arrow-key picker + hidden password prompt), `on`, `off`, `forget`. Linux and Windows fully; macOS without `list`. |
|
|
72
73
|
|
|
73
74
|
## Examples
|
|
74
75
|
|
|
75
76
|
```bash
|
|
76
77
|
# Trim video from 5s to 20s
|
|
77
|
-
|
|
78
|
+
editvideo movie.mp4 --start 5.0 --end 20.0
|
|
78
79
|
|
|
79
80
|
# Open interactive clip editor in the browser
|
|
80
|
-
|
|
81
|
+
editvideo movie.mp4 --gui
|
|
81
82
|
|
|
82
83
|
# Convert video to GIF (3.5s–10s at 15 fps)
|
|
83
84
|
video2gif movie.mp4 --start 3.5 --end 10.0 --fps 15
|
|
@@ -107,6 +108,18 @@ netscan --lookup
|
|
|
107
108
|
# Scan a specific subnet, faster, without hostname lookups
|
|
108
109
|
netscan --network 192.168.1.0/24 --timeout 0.5 --no-resolve
|
|
109
110
|
|
|
111
|
+
# Map every network segment and address around you (passive, ~2s)
|
|
112
|
+
netsurvey
|
|
113
|
+
|
|
114
|
+
# Segments and conflicts only, no address lists
|
|
115
|
+
netsurvey --summary
|
|
116
|
+
|
|
117
|
+
# Also ping-sweep your ranges and the discovered ones
|
|
118
|
+
netsurvey --sweep
|
|
119
|
+
|
|
120
|
+
# Check specific ranges before assigning them at a venue
|
|
121
|
+
netsurvey --include 192.168.1.0/24 --include 10.0.0.0/24
|
|
122
|
+
|
|
110
123
|
# Show the Wi-Fi networks in range (Linux / Windows)
|
|
111
124
|
wifi list
|
|
112
125
|
|
|
@@ -129,6 +142,58 @@ wifi forget OldCafe
|
|
|
129
142
|
> laptops that use a randomized/private MAC show up as `(private)` and can't be
|
|
130
143
|
> attributed to a vendor.
|
|
131
144
|
|
|
145
|
+
### Surveying a shared network
|
|
146
|
+
|
|
147
|
+
`netscan` answers "who is on **my** subnet?". `netsurvey` answers "what is on
|
|
148
|
+
this **wire**?" — the question that matters in an exhibition hall, an office, or
|
|
149
|
+
a co-working space where other people's routers hand out their own ranges:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
INTERFACES
|
|
153
|
+
en0 192.168.0.122/24 gateway 192.168.0.1
|
|
154
|
+
utun6 10.2.244.44/32
|
|
155
|
+
|
|
156
|
+
SEGMENTS (3 total, 2 yours, 9 address(es) in use)
|
|
157
|
+
192.168.0.0/24 local (en0) swept 6 host(s)
|
|
158
|
+
192.168.0.1 06:f2:67:75:4d:e2 gateway,arp,ping gateway / router
|
|
159
|
+
192.168.0.122 4e:e5:41:93:7c:1f self,arp,mdns this device
|
|
160
|
+
...
|
|
161
|
+
10.77.0.0/24 foreign passive 2 host(s)
|
|
162
|
+
10.77.0.9 de:ad:be:ef:00:01 ssdp,arp
|
|
163
|
+
|
|
164
|
+
CONFLICTS (2)
|
|
165
|
+
! [duplicate-ip] 192.168.0.50 answers from 2 MAC addresses (…) — two devices claim the same address
|
|
166
|
+
- [shared-l2] 1 foreign segment(s) share this link: 10.77.0.0/24
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Three independent sources are combined, so segments you have no address in still
|
|
170
|
+
show up:
|
|
171
|
+
|
|
172
|
+
- **every local interface** with its real netmask (`ip` / `ifconfig` / `ipconfig`);
|
|
173
|
+
- the **ARP / neighbour cache**, which lists link neighbours *regardless of
|
|
174
|
+
subnet* — this is what exposes somebody else's range on the same switch;
|
|
175
|
+
- **SSDP and mDNS** answers, sent out of each interface (skip with `--no-passive`).
|
|
176
|
+
|
|
177
|
+
The default pass is passive and takes about two seconds. `--sweep` additionally
|
|
178
|
+
ping-sweeps your ranges and the discovered ones so each address list is complete;
|
|
179
|
+
`--include CIDR` sweeps a specific range you are about to assign. Ranges larger
|
|
180
|
+
than `--max-sweep` (4096 hosts) are narrowed to the /24 around your own address
|
|
181
|
+
or skipped, with a note on stderr.
|
|
182
|
+
|
|
183
|
+
Reported conflicts:
|
|
184
|
+
|
|
185
|
+
| Kind | Meaning |
|
|
186
|
+
|------|---------|
|
|
187
|
+
| `duplicate-ip` | One address answers from two MACs — two devices claim it. |
|
|
188
|
+
| `overlap` | A foreign range, or a second local interface (VPN!), overlaps one of your subnets. |
|
|
189
|
+
| `no-dhcp` | An interface self-assigned a `169.254.x.x` address; no DHCP server answered. |
|
|
190
|
+
| `router-bridge` | One MAC answers for addresses in several segments — a router joining them. |
|
|
191
|
+
| `shared-l2` | Foreign segments are present on your link (informational). |
|
|
192
|
+
|
|
193
|
+
> Survey only networks you are entitled to. `netsurvey` sends ordinary pings and
|
|
194
|
+
> standard discovery multicast — nothing privileged — but probing networks you
|
|
195
|
+
> don't administer may still violate policy or law.
|
|
196
|
+
|
|
132
197
|
### Wi-Fi
|
|
133
198
|
|
|
134
199
|
`wifi connect` lists everything in range — move with ↑/↓, press Enter to join,
|
|
@@ -208,7 +273,7 @@ dependency for one platform, devbits doesn't scan on macOS at all.
|
|
|
208
273
|
When `-o` / `--output` is omitted, the output filename is derived from the input:
|
|
209
274
|
|
|
210
275
|
```
|
|
211
|
-
|
|
276
|
+
editvideo movie.mp4 → movie_clip.mp4
|
|
212
277
|
video2gif movie.mp4 → movie.gif
|
|
213
278
|
resizeimage photo.jpg → photo_resized.jpg
|
|
214
279
|
recolor logo.png → logo_revised.png
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
+
import ipaddress
|
|
4
5
|
import os
|
|
5
6
|
import shutil
|
|
6
7
|
import sys
|
|
@@ -33,6 +34,9 @@ _ANSI = {
|
|
|
33
34
|
"signal": "\033[32m",
|
|
34
35
|
"lock": "\033[33m",
|
|
35
36
|
"dim": "\033[2m",
|
|
37
|
+
"segment": "\033[1;36m",
|
|
38
|
+
"warn": "\033[1;31m",
|
|
39
|
+
"note": "\033[2m",
|
|
36
40
|
"reset": "\033[0m",
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -191,20 +195,20 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
191
195
|
help="Output size as 'width,height' in pixels, e.g. 640,360.")
|
|
192
196
|
p.set_defaults(func=cmd_video2gif)
|
|
193
197
|
|
|
194
|
-
# ──
|
|
198
|
+
# ── editvideo ──────────────────────────────────────────────
|
|
195
199
|
p = sub.add_parser(
|
|
196
|
-
"
|
|
197
|
-
help="
|
|
200
|
+
"editvideo",
|
|
201
|
+
help="Edit (trim) a video by time range or frame range.",
|
|
198
202
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
199
203
|
description=(
|
|
200
204
|
"Trim a portion of a video. You can specify the range in seconds\n"
|
|
201
205
|
"(--start / --end) or in frame indices (--start-frame / --end-frame).\n"
|
|
202
206
|
"If no range is given, the interactive browser-based editor opens.\n\n"
|
|
203
207
|
"Examples:\n"
|
|
204
|
-
" devbits
|
|
205
|
-
" devbits
|
|
206
|
-
" devbits
|
|
207
|
-
" devbits
|
|
208
|
+
" devbits editvideo movie.mp4 # opens the GUI editor\n"
|
|
209
|
+
" devbits editvideo movie.mp4 --start 5.0 --end 20.0\n"
|
|
210
|
+
" devbits editvideo movie.mp4 --start-frame 150 --end-frame 600\n"
|
|
211
|
+
" devbits editvideo --gui # GUI with no initial video"
|
|
208
212
|
),
|
|
209
213
|
)
|
|
210
214
|
p.add_argument("video", type=Path, nargs="?", default=None,
|
|
@@ -221,7 +225,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
221
225
|
help="End frame index (0-based, inclusive).")
|
|
222
226
|
p.add_argument("--gui", action="store_true",
|
|
223
227
|
help="Open interactive browser-based clip editor.")
|
|
224
|
-
p.set_defaults(func=
|
|
228
|
+
p.set_defaults(func=cmd_editvideo)
|
|
225
229
|
|
|
226
230
|
# ── resizevideo ────────────────────────────────────────────
|
|
227
231
|
p = sub.add_parser(
|
|
@@ -491,6 +495,58 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
491
495
|
help="Disable colored output (also honors NO_COLOR).")
|
|
492
496
|
p.set_defaults(func=cmd_netscan)
|
|
493
497
|
|
|
498
|
+
# ── netsurvey ──────────────────────────────────────────────
|
|
499
|
+
p = sub.add_parser(
|
|
500
|
+
"netsurvey",
|
|
501
|
+
help="Map every network segment and address in use around you.",
|
|
502
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
503
|
+
description=(
|
|
504
|
+
"Survey the whole environment instead of a single subnet: list your own\n"
|
|
505
|
+
"interfaces and their ranges, every segment visible on the same wire, the\n"
|
|
506
|
+
"addresses in use in each, and the clashes between them.\n\n"
|
|
507
|
+
"Built for shared networks — a trade-show hall, an office, a co-working\n"
|
|
508
|
+
"space — where someone else's router hands out an overlapping range and\n"
|
|
509
|
+
"traffic quietly goes to the wrong place. Reports duplicate IPs, ranges\n"
|
|
510
|
+
"that overlap your own, links with no DHCP, and routers bridging two\n"
|
|
511
|
+
"segments.\n\n"
|
|
512
|
+
"The default pass is passive and takes a couple of seconds: it reads the\n"
|
|
513
|
+
"ARP / neighbour cache (which lists neighbours on *foreign* subnets too)\n"
|
|
514
|
+
"and asks for SSDP and mDNS answers. --sweep additionally ping-sweeps\n"
|
|
515
|
+
"your ranges and the discovered ones so each address list is complete.\n\n"
|
|
516
|
+
"Survey only networks you are entitled to; probing networks you don't\n"
|
|
517
|
+
"administer may violate policy or law.\n\n"
|
|
518
|
+
"Examples:\n"
|
|
519
|
+
" devbits netsurvey # quick passive map\n"
|
|
520
|
+
" devbits netsurvey --summary # segments only\n"
|
|
521
|
+
" devbits netsurvey --sweep # also enumerate every host\n"
|
|
522
|
+
" devbits netsurvey --include 192.168.1.0/24 --include 10.0.0.0/24\n"
|
|
523
|
+
" devbits netsurvey --sweep --group-prefix 16 --timeout 0.4"
|
|
524
|
+
),
|
|
525
|
+
)
|
|
526
|
+
p.add_argument("--sweep", action="store_true",
|
|
527
|
+
help="Also ping-sweep your own and the discovered segments (slower, complete).")
|
|
528
|
+
p.add_argument("--include", metavar="CIDR", action="append", default=[],
|
|
529
|
+
help="Extra range to sweep, e.g. 10.0.0.0/24. Repeatable.")
|
|
530
|
+
p.add_argument("--group-prefix", type=int, default=24, metavar="N",
|
|
531
|
+
help="Prefix used to group addresses outside your own subnets. Default: 24")
|
|
532
|
+
p.add_argument("--timeout", type=float, default=0.6,
|
|
533
|
+
help="Per-host ping timeout in seconds. Default: 0.6")
|
|
534
|
+
p.add_argument("--workers", type=int, default=128,
|
|
535
|
+
help="Number of concurrent ping workers. Default: 128")
|
|
536
|
+
p.add_argument("--discover-timeout", type=float, default=2.0, metavar="SECONDS",
|
|
537
|
+
help="How long to listen for SSDP / mDNS answers. Default: 2.0")
|
|
538
|
+
p.add_argument("--no-passive", action="store_true",
|
|
539
|
+
help="Skip the multicast discovery step; read the ARP cache only.")
|
|
540
|
+
p.add_argument("--resolve", action="store_true",
|
|
541
|
+
help="Reverse-DNS each address for its hostname (slower).")
|
|
542
|
+
p.add_argument("--max-sweep", type=int, default=4096, metavar="HOSTS",
|
|
543
|
+
help="Refuse to sweep a range larger than this. Default: 4096")
|
|
544
|
+
p.add_argument("--summary", action="store_true",
|
|
545
|
+
help="List segments and conflicts only, without the addresses.")
|
|
546
|
+
p.add_argument("--no-color", action="store_true",
|
|
547
|
+
help="Disable colored output (also honors NO_COLOR).")
|
|
548
|
+
p.set_defaults(func=cmd_netsurvey)
|
|
549
|
+
|
|
494
550
|
# ── wifi ───────────────────────────────────────────────────
|
|
495
551
|
p = sub.add_parser(
|
|
496
552
|
"wifi",
|
|
@@ -644,7 +700,7 @@ def cmd_video2gif(args: argparse.Namespace) -> None:
|
|
|
644
700
|
print(video_to_gif(video, output, args.fps, args.start, args.end, args.size))
|
|
645
701
|
|
|
646
702
|
|
|
647
|
-
def
|
|
703
|
+
def cmd_editvideo(args: argparse.Namespace) -> None:
|
|
648
704
|
has_range = any(v is not None for v in (args.start, args.end, args.start_frame, args.end_frame))
|
|
649
705
|
# Open the interactive editor when --gui is set, or by default when a video
|
|
650
706
|
# is provided without an explicit trim range.
|
|
@@ -741,8 +797,6 @@ def cmd_samplefiles(args: argparse.Namespace) -> None:
|
|
|
741
797
|
|
|
742
798
|
|
|
743
799
|
def cmd_netscan(args: argparse.Namespace) -> None:
|
|
744
|
-
import ipaddress
|
|
745
|
-
|
|
746
800
|
color = _use_color(False if args.no_color else None)
|
|
747
801
|
network = ipaddress.ip_network(args.network, strict=False) if args.network else None
|
|
748
802
|
|
|
@@ -771,6 +825,85 @@ def cmd_netscan(args: argparse.Namespace) -> None:
|
|
|
771
825
|
print(f"Found {len(devices)} device(s).")
|
|
772
826
|
|
|
773
827
|
|
|
828
|
+
def _survey_source_label(sources: set[str], width: int = 22) -> str:
|
|
829
|
+
"""Condense a device's discovery sources into a stable, readable column."""
|
|
830
|
+
order = ["self", "gateway", "arp", "ping", "ssdp", "mdns"]
|
|
831
|
+
label = ",".join(source for source in order if source in sources) or "-"
|
|
832
|
+
if len(label) > width - 1:
|
|
833
|
+
label = label[: width - 2] + "…"
|
|
834
|
+
return f"{label:<{width}}"
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def cmd_netsurvey(args: argparse.Namespace) -> None:
|
|
838
|
+
from .network import survey_network
|
|
839
|
+
|
|
840
|
+
color = _use_color(False if args.no_color else None)
|
|
841
|
+
if not 8 <= args.group_prefix <= 32:
|
|
842
|
+
raise ValueError("--group-prefix must be between 8 and 32")
|
|
843
|
+
|
|
844
|
+
survey, notes = survey_network(
|
|
845
|
+
include=args.include,
|
|
846
|
+
group_prefix=args.group_prefix,
|
|
847
|
+
sweep=args.sweep,
|
|
848
|
+
passive=not args.no_passive,
|
|
849
|
+
timeout=args.timeout,
|
|
850
|
+
workers=args.workers,
|
|
851
|
+
discover_timeout=args.discover_timeout,
|
|
852
|
+
resolve=args.resolve,
|
|
853
|
+
max_sweep_hosts=args.max_sweep,
|
|
854
|
+
progress=lambda message: print(message, file=sys.stderr),
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
print(_colorize("INTERFACES", "header", color))
|
|
858
|
+
for nic in survey.interfaces:
|
|
859
|
+
note = "gateway " + survey.gateway if survey.gateway and \
|
|
860
|
+
ipaddress.ip_address(survey.gateway) in nic.network else ""
|
|
861
|
+
if nic.is_link_local:
|
|
862
|
+
note = "self-assigned (no DHCP)"
|
|
863
|
+
name_col = _colorize(f"{nic.name:<12}", "self" if nic.ip == survey.self_ip else "dim", color)
|
|
864
|
+
print(f" {name_col}{f'{nic.ip}/{nic.prefix}':<22}{_colorize(note, 'note', color)}".rstrip())
|
|
865
|
+
|
|
866
|
+
print()
|
|
867
|
+
local_count = sum(1 for segment in survey.segments if segment.is_local)
|
|
868
|
+
print(_colorize(
|
|
869
|
+
f"SEGMENTS ({len(survey.segments)} total, {local_count} yours, "
|
|
870
|
+
f"{survey.device_count} address(es) in use)", "header", color,
|
|
871
|
+
))
|
|
872
|
+
for segment in survey.segments:
|
|
873
|
+
scope = f"local ({', '.join(segment.interfaces)})" if segment.is_local else "foreign"
|
|
874
|
+
how = "swept" if segment.swept else "passive"
|
|
875
|
+
head = f" {str(segment.network):<20}{scope:<24}{how:<10}{len(segment.devices)} host(s)"
|
|
876
|
+
print(_colorize(head.rstrip(), "segment" if segment.is_local else "warn", color))
|
|
877
|
+
if args.summary:
|
|
878
|
+
continue
|
|
879
|
+
for device in segment.devices:
|
|
880
|
+
note = "this device" if device.is_self else ("gateway / router" if device.is_gateway else "")
|
|
881
|
+
kind = "self" if device.is_self else ("gateway" if device.is_gateway else None)
|
|
882
|
+
ip_col = _colorize(f"{device.ip:<16}", kind, color) if kind else f"{device.ip:<16}"
|
|
883
|
+
mac_col = _colorize(f"{device.mac or '-':<20}", "mac", color)
|
|
884
|
+
host_col = _colorize(f"{device.hostname or '-':<28}", "host", color) if args.resolve else ""
|
|
885
|
+
seen_col = _colorize(_survey_source_label(device.sources), "note", color)
|
|
886
|
+
print(f" {ip_col}{mac_col}{host_col}{seen_col}{note}".rstrip())
|
|
887
|
+
|
|
888
|
+
print()
|
|
889
|
+
if survey.conflicts:
|
|
890
|
+
print(_colorize(f"CONFLICTS ({len(survey.conflicts)})", "header", color))
|
|
891
|
+
for conflict in survey.conflicts:
|
|
892
|
+
marker = "!" if conflict.kind != "shared-l2" else "-"
|
|
893
|
+
kind = "warn" if conflict.kind != "shared-l2" else "note"
|
|
894
|
+
print(_colorize(f" {marker} [{conflict.kind}] {conflict.message}", kind, color))
|
|
895
|
+
else:
|
|
896
|
+
print("No address or range conflicts detected.")
|
|
897
|
+
|
|
898
|
+
for note in notes:
|
|
899
|
+
print(_colorize(f" note: {note}", "note", color), file=sys.stderr)
|
|
900
|
+
if not args.sweep and not args.include:
|
|
901
|
+
print(_colorize(
|
|
902
|
+
"Passive pass only — run with --sweep to enumerate every address in each segment.",
|
|
903
|
+
"note", color,
|
|
904
|
+
), file=sys.stderr)
|
|
905
|
+
|
|
906
|
+
|
|
774
907
|
# ---------------------------------------------------------------------------
|
|
775
908
|
# wifi
|
|
776
909
|
# ---------------------------------------------------------------------------
|
|
@@ -1008,6 +1141,15 @@ def cmd_wifi_forget(args: argparse.Namespace) -> None:
|
|
|
1008
1141
|
|
|
1009
1142
|
|
|
1010
1143
|
def main(argv: list[str] | None = None) -> int:
|
|
1144
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
1145
|
+
if argv and argv[0] == "clipvideo":
|
|
1146
|
+
# Deprecated alias kept so existing invocations keep working.
|
|
1147
|
+
print(
|
|
1148
|
+
"Note: 'clipvideo' has been renamed to 'editvideo' and will be "
|
|
1149
|
+
"removed in a future release. Please use 'editvideo' instead.",
|
|
1150
|
+
file=sys.stderr,
|
|
1151
|
+
)
|
|
1152
|
+
argv[0] = "editvideo"
|
|
1011
1153
|
parser = build_parser()
|
|
1012
1154
|
args = parser.parse_args(argv)
|
|
1013
1155
|
try:
|