dlna-here 0.1.0__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.
- dlna_here-0.1.0/.github/dependabot.yml +10 -0
- dlna_here-0.1.0/.github/workflows/ci.yml +88 -0
- dlna_here-0.1.0/.github/workflows/release.yml +103 -0
- dlna_here-0.1.0/.gitignore +8 -0
- dlna_here-0.1.0/LICENSE +21 -0
- dlna_here-0.1.0/PKG-INFO +217 -0
- dlna_here-0.1.0/README.md +192 -0
- dlna_here-0.1.0/ROADMAP.md +315 -0
- dlna_here-0.1.0/pyproject.toml +41 -0
- dlna_here-0.1.0/src/dlna_here/__init__.py +3 -0
- dlna_here-0.1.0/src/dlna_here/__main__.py +4 -0
- dlna_here-0.1.0/src/dlna_here/catalog.py +1258 -0
- dlna_here-0.1.0/src/dlna_here/cli.py +170 -0
- dlna_here-0.1.0/src/dlna_here/dlna.py +15 -0
- dlna_here-0.1.0/src/dlna_here/eventing.py +231 -0
- dlna_here-0.1.0/src/dlna_here/interfaces.py +198 -0
- dlna_here-0.1.0/src/dlna_here/keyboard.py +132 -0
- dlna_here-0.1.0/src/dlna_here/monitor.py +68 -0
- dlna_here-0.1.0/src/dlna_here/server.py +778 -0
- dlna_here-0.1.0/src/dlna_here/ssdp.py +613 -0
- dlna_here-0.1.0/src/dlna_here/streaming.py +493 -0
- dlna_here-0.1.0/src/dlna_here/upnp_xml.py +553 -0
- dlna_here-0.1.0/tests/smoke_test.py +50 -0
- dlna_here-0.1.0/tests/test_catalog.py +653 -0
- dlna_here-0.1.0/tests/test_cli.py +142 -0
- dlna_here-0.1.0/tests/test_eventing.py +121 -0
- dlna_here-0.1.0/tests/test_interfaces.py +76 -0
- dlna_here-0.1.0/tests/test_keyboard.py +126 -0
- dlna_here-0.1.0/tests/test_monitor.py +56 -0
- dlna_here-0.1.0/tests/test_server.py +716 -0
- dlna_here-0.1.0/tests/test_ssdp.py +289 -0
- dlna_here-0.1.0/tests/test_streaming.py +331 -0
- dlna_here-0.1.0/tests/test_upnp_xml.py +344 -0
- dlna_here-0.1.0/uv.lock +23 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
test:
|
|
18
|
+
name: ${{ matrix.os }} / Python ${{ matrix.python-version }}
|
|
19
|
+
runs-on: ${{ matrix.os }}
|
|
20
|
+
timeout-minutes: 15
|
|
21
|
+
strategy:
|
|
22
|
+
fail-fast: false
|
|
23
|
+
matrix:
|
|
24
|
+
os:
|
|
25
|
+
- ubuntu-latest
|
|
26
|
+
- windows-latest
|
|
27
|
+
- macos-latest
|
|
28
|
+
python-version:
|
|
29
|
+
- "3.11"
|
|
30
|
+
- "3.13"
|
|
31
|
+
|
|
32
|
+
steps:
|
|
33
|
+
- name: Check out source
|
|
34
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
35
|
+
with:
|
|
36
|
+
persist-credentials: false
|
|
37
|
+
|
|
38
|
+
- name: Install uv and Python
|
|
39
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
40
|
+
with:
|
|
41
|
+
version: "0.11.32"
|
|
42
|
+
python-version: ${{ matrix.python-version }}
|
|
43
|
+
enable-cache: false
|
|
44
|
+
|
|
45
|
+
- name: Install the project
|
|
46
|
+
run: uv sync --locked
|
|
47
|
+
|
|
48
|
+
- name: Run unit tests
|
|
49
|
+
run: uv run --frozen python -m unittest discover -s tests -v
|
|
50
|
+
|
|
51
|
+
quality:
|
|
52
|
+
name: Lint, format, and build
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
timeout-minutes: 15
|
|
55
|
+
|
|
56
|
+
steps:
|
|
57
|
+
- name: Check out source
|
|
58
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
59
|
+
with:
|
|
60
|
+
persist-credentials: false
|
|
61
|
+
|
|
62
|
+
- name: Install uv and Python
|
|
63
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
64
|
+
with:
|
|
65
|
+
version: "0.11.32"
|
|
66
|
+
python-version: "3.13"
|
|
67
|
+
enable-cache: false
|
|
68
|
+
|
|
69
|
+
- name: Lint
|
|
70
|
+
run: uvx --from ruff==0.16.0 ruff check .
|
|
71
|
+
|
|
72
|
+
- name: Check formatting
|
|
73
|
+
run: uvx --from ruff==0.16.0 ruff format --check .
|
|
74
|
+
|
|
75
|
+
- name: Build distributions
|
|
76
|
+
run: uv build --no-sources
|
|
77
|
+
|
|
78
|
+
- name: Smoke-test wheel
|
|
79
|
+
shell: bash
|
|
80
|
+
run: |
|
|
81
|
+
wheel="$(find dist -maxdepth 1 -name '*.whl' -print -quit)"
|
|
82
|
+
uv run --isolated --no-project --with "$wheel" python tests/smoke_test.py
|
|
83
|
+
|
|
84
|
+
- name: Smoke-test source distribution
|
|
85
|
+
shell: bash
|
|
86
|
+
run: |
|
|
87
|
+
sdist="$(find dist -maxdepth 1 -name '*.tar.gz' -print -quit)"
|
|
88
|
+
uv run --isolated --no-project --with "$sdist" python tests/smoke_test.py
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
name: Build and verify distributions
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
timeout-minutes: 15
|
|
16
|
+
|
|
17
|
+
steps:
|
|
18
|
+
- name: Check out source
|
|
19
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
|
|
24
|
+
- name: Install uv and Python
|
|
25
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
26
|
+
with:
|
|
27
|
+
version: "0.11.32"
|
|
28
|
+
python-version: "3.13"
|
|
29
|
+
enable-cache: false
|
|
30
|
+
|
|
31
|
+
- name: Verify release tag
|
|
32
|
+
shell: bash
|
|
33
|
+
run: |
|
|
34
|
+
expected_tag="v$(uv version --short)"
|
|
35
|
+
if [[ "$GITHUB_REF_NAME" != "$expected_tag" ]]; then
|
|
36
|
+
echo "Release tag $GITHUB_REF_NAME does not match $expected_tag." >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
git fetch --no-tags origin main:refs/remotes/origin/main
|
|
40
|
+
if ! git merge-base --is-ancestor "$GITHUB_SHA" refs/remotes/origin/main; then
|
|
41
|
+
echo "Tagged commit is not on the main branch." >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
- name: Install the project
|
|
46
|
+
run: uv sync --locked
|
|
47
|
+
|
|
48
|
+
- name: Run unit tests
|
|
49
|
+
run: uv run --frozen python -m unittest discover -s tests -v
|
|
50
|
+
|
|
51
|
+
- name: Lint
|
|
52
|
+
run: uvx --from ruff==0.16.0 ruff check .
|
|
53
|
+
|
|
54
|
+
- name: Check formatting
|
|
55
|
+
run: uvx --from ruff==0.16.0 ruff format --check .
|
|
56
|
+
|
|
57
|
+
- name: Build distributions
|
|
58
|
+
run: uv build --no-sources
|
|
59
|
+
|
|
60
|
+
- name: Smoke-test wheel
|
|
61
|
+
shell: bash
|
|
62
|
+
run: |
|
|
63
|
+
wheel="$(find dist -maxdepth 1 -name '*.whl' -print -quit)"
|
|
64
|
+
uv run --isolated --no-project --with "$wheel" python tests/smoke_test.py
|
|
65
|
+
|
|
66
|
+
- name: Smoke-test source distribution
|
|
67
|
+
shell: bash
|
|
68
|
+
run: |
|
|
69
|
+
sdist="$(find dist -maxdepth 1 -name '*.tar.gz' -print -quit)"
|
|
70
|
+
uv run --isolated --no-project --with "$sdist" python tests/smoke_test.py
|
|
71
|
+
|
|
72
|
+
- name: Upload distributions
|
|
73
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
74
|
+
with:
|
|
75
|
+
name: python-package-distributions
|
|
76
|
+
path: |
|
|
77
|
+
dist/*.whl
|
|
78
|
+
dist/*.tar.gz
|
|
79
|
+
if-no-files-found: error
|
|
80
|
+
retention-days: 7
|
|
81
|
+
|
|
82
|
+
publish:
|
|
83
|
+
name: Publish distributions
|
|
84
|
+
needs: build
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
environment:
|
|
87
|
+
name: pypi
|
|
88
|
+
url: https://pypi.org/p/dlna-here
|
|
89
|
+
permissions:
|
|
90
|
+
id-token: write
|
|
91
|
+
|
|
92
|
+
steps:
|
|
93
|
+
- name: Download distributions
|
|
94
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
95
|
+
with:
|
|
96
|
+
name: python-package-distributions
|
|
97
|
+
path: dist/
|
|
98
|
+
|
|
99
|
+
- name: Publish to PyPI
|
|
100
|
+
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
|
101
|
+
with:
|
|
102
|
+
packages-dir: dist/
|
|
103
|
+
attestations: true
|
dlna_here-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 John Paul Ellis
|
|
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.
|
dlna_here-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dlna-here
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Serve a local media file or directory as a no-transcoding UPnP AV/DLNA server
|
|
5
|
+
Project-URL: Homepage, https://github.com/pseudosavant/dlna-here
|
|
6
|
+
Project-URL: Repository, https://github.com/pseudosavant/dlna-here
|
|
7
|
+
Project-URL: Issues, https://github.com/pseudosavant/dlna-here/issues
|
|
8
|
+
Author: John Paul Ellis
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,dlna,media-server,streaming,upnp
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Multimedia
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: ifaddr<1,>=0.2.0
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# dlna-here
|
|
27
|
+
|
|
28
|
+
`dlna-here` is a small command-line UPnP/DLNA media server for sharing a local
|
|
29
|
+
folder—or one media file—on your local network. It serves videos, audio, and
|
|
30
|
+
images exactly as they are, with no transcoding and no persistent media
|
|
31
|
+
database.
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
Once [`uv`](https://docs.astral.sh/uv/) is installed, open a terminal in any
|
|
36
|
+
media folder and run:
|
|
37
|
+
|
|
38
|
+
```console
|
|
39
|
+
uvx dlna-here
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That is it. There is no separate `dlna-here` installation or configuration
|
|
43
|
+
step. Open the UPnP/DLNA browser on a player connected to the same network and
|
|
44
|
+
look for `<folder> on <computer>`.
|
|
45
|
+
|
|
46
|
+
Press `Q` or `q` in the terminal to stop the server. `Ctrl+C` works too. On a
|
|
47
|
+
terminal that does not support immediate single-key input, enter `q` and press
|
|
48
|
+
Enter.
|
|
49
|
+
|
|
50
|
+
You can also serve a different folder:
|
|
51
|
+
|
|
52
|
+
```console
|
|
53
|
+
uvx dlna-here "D:\Videos"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or expose one file without making its containing folder or sibling files
|
|
57
|
+
browsable:
|
|
58
|
+
|
|
59
|
+
```console
|
|
60
|
+
uvx dlna-here "D:\Videos\Movie.mp4"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## What it does
|
|
64
|
+
|
|
65
|
+
- Preserves the filesystem's folder hierarchy.
|
|
66
|
+
- Reflects additions, removals, and renames without restarting the server.
|
|
67
|
+
- Serves original files with HTTP byte-range support for playback and seeking.
|
|
68
|
+
- Uses filenames and filesystem metadata for browsing; it does not read media
|
|
69
|
+
contents before a player requests a file.
|
|
70
|
+
- Excludes hidden files and directories.
|
|
71
|
+
- Shows media names without the final filename extension by default.
|
|
72
|
+
- Follows symbolic links and Windows junctions by default, with cycle
|
|
73
|
+
protection.
|
|
74
|
+
- Supports Windows, Linux, and macOS. Windows is the primary platform.
|
|
75
|
+
|
|
76
|
+
## Command-line arguments
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
dlna-here [path] [options]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| Argument | Description |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `[path]` | File or folder to serve. The current directory is used when omitted. A file path exposes only that supported media file. |
|
|
85
|
+
| `-h`, `--help` | Show the complete command help and exit. |
|
|
86
|
+
| `--interface auto\|NAME\|IPv4` | Select the IPv4 network interface. `auto` is the default and prefers the interface used by the system's default route. A displayed interface name or a concrete IPv4 address can also be used. |
|
|
87
|
+
| `--port PORT` | Set the HTTP media port. The default, `0`, asks the operating system to choose an available port. |
|
|
88
|
+
| `--name NAME` | Set the friendly server name displayed by media players. The default is `<file-or-folder> on <computer>`. |
|
|
89
|
+
| `--show-extensions` | Show full filenames such as `Movie.mp4` instead of `Movie`. This also affects the automatic server name in single-file mode. Folder names are never changed. |
|
|
90
|
+
| `--no-follow-links` | Omit symbolic links and Windows junctions. By default they are followed, including links whose targets are outside the served folder. |
|
|
91
|
+
| `--list-interfaces` | List the IPv4 interfaces available for `--interface` and exit. |
|
|
92
|
+
| `--verbose` | Print protocol, filesystem, request, and transfer diagnostics. |
|
|
93
|
+
| `--version` | Print the `dlna-here` version and exit. |
|
|
94
|
+
| `--about` | Print project and license information and exit. |
|
|
95
|
+
|
|
96
|
+
For example:
|
|
97
|
+
|
|
98
|
+
```console
|
|
99
|
+
uvx dlna-here "D:\Videos" --name "Living room videos"
|
|
100
|
+
uvx dlna-here --list-interfaces
|
|
101
|
+
uvx dlna-here --interface "Wi-Fi"
|
|
102
|
+
uvx dlna-here --interface 192.168.1.25 --port 8200
|
|
103
|
+
uvx dlna-here --show-extensions --no-follow-links
|
|
104
|
+
uvx dlna-here --verbose
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The HTTP server binds to the selected IPv4 address. UPnP discovery continues
|
|
108
|
+
to use its standard IPv4 multicast address and UDP port 1900.
|
|
109
|
+
|
|
110
|
+
## Supported media files
|
|
111
|
+
|
|
112
|
+
File extensions are matched case-insensitively.
|
|
113
|
+
|
|
114
|
+
- Video: `.3g2`, `.3gp`, `.asf`, `.avi`, `.divx`, `.flv`, `.m2t`, `.m2ts`,
|
|
115
|
+
`.m4v`, `.mkv`, `.mov`, `.mp4`, `.mpe`, `.mpeg`, `.mpg`, `.mts`, `.ogm`,
|
|
116
|
+
`.ogv`, `.ts`, `.vob`, `.webm`, `.wmv`
|
|
117
|
+
- Audio: `.aac`, `.aif`, `.aiff`, `.alac`, `.flac`, `.m4a`, `.mp2`, `.mp3`,
|
|
118
|
+
`.oga`, `.ogg`, `.opus`, `.wav`, `.wave`, `.wma`
|
|
119
|
+
- Images: `.avif`, `.bmp`, `.gif`, `.heic`, `.heif`, `.jpe`, `.jpeg`, `.jpg`,
|
|
120
|
+
`.png`, `.thm`, `.tif`, `.tiff`, `.webp`
|
|
121
|
+
|
|
122
|
+
Support here means that `dlna-here` recognizes the extension, advertises an
|
|
123
|
+
appropriate media type, and serves the original bytes. It does not inspect or
|
|
124
|
+
convert the container, video codec, audio codec, or image encoding. The
|
|
125
|
+
receiving player must support the actual file.
|
|
126
|
+
|
|
127
|
+
## Sidecar thumbnails and artwork
|
|
128
|
+
|
|
129
|
+
`dlna-here` can advertise existing `.jpg`, `.jpeg`, `.png`, or `.thm` images
|
|
130
|
+
as sidecar artwork for video and audio without opening the media file.
|
|
131
|
+
|
|
132
|
+
The simplest arrangement uses matching filenames:
|
|
133
|
+
|
|
134
|
+
```text
|
|
135
|
+
Movies/
|
|
136
|
+
├── Movie.mp4
|
|
137
|
+
└── Movie.jpg
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Artwork is selected in this order:
|
|
141
|
+
|
|
142
|
+
1. Same-stem artwork such as `Movie.jpg`
|
|
143
|
+
2. Item-specific artwork such as `Movie-poster.png` or `Movie.poster.jpg`
|
|
144
|
+
3. Folder artwork named `cover`, `folder`, `album`, or `poster`
|
|
145
|
+
|
|
146
|
+
The original artwork file is served without resizing or converting it.
|
|
147
|
+
Artwork selected for a video is hidden from the ordinary image listing.
|
|
148
|
+
Single-file mode can use sidecar artwork from the selected file's directory
|
|
149
|
+
without exposing any other files there.
|
|
150
|
+
|
|
151
|
+
To create missing same-stem JPEG thumbnails recursively for a folder of
|
|
152
|
+
videos, use the helper script from
|
|
153
|
+
[`player.html`](https://github.com/pseudosavant/player.html). It requires
|
|
154
|
+
`ffmpeg` on `PATH`, skips videos that already have a thumbnail, and does not
|
|
155
|
+
overwrite existing artwork:
|
|
156
|
+
|
|
157
|
+
```console
|
|
158
|
+
uv run https://raw.githubusercontent.com/pseudosavant/player.html/main/prerender-video-thumbnails.py "D:\Videos"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## What it deliberately does not do
|
|
162
|
+
|
|
163
|
+
- It does not transcode video or audio.
|
|
164
|
+
- It does not remux media or mux subtitles into a stream.
|
|
165
|
+
- It does not expose or manage external subtitle files.
|
|
166
|
+
- It does not build or maintain a persistent media database.
|
|
167
|
+
- It does not have a web interface, user accounts, or authentication.
|
|
168
|
+
- It does not have persistent configuration; each run is controlled by
|
|
169
|
+
command-line arguments.
|
|
170
|
+
|
|
171
|
+
The catalog is built from the live filesystem as players browse it. Clients
|
|
172
|
+
receive the original file and decide whether they can play it.
|
|
173
|
+
|
|
174
|
+
## Network security
|
|
175
|
+
|
|
176
|
+
`dlna-here` is an unauthenticated server intended for a trusted local network.
|
|
177
|
+
Anyone who can reach it can browse and request the files it exposes. Do not
|
|
178
|
+
publish its HTTP port to the Internet.
|
|
179
|
+
|
|
180
|
+
## Tested players
|
|
181
|
+
|
|
182
|
+
`dlna-here` has been tested with:
|
|
183
|
+
|
|
184
|
+
- VLC
|
|
185
|
+
- Windows Media Player Legacy (`wmplayer.exe`)
|
|
186
|
+
- Roku Media Player
|
|
187
|
+
- Xbox Media Player
|
|
188
|
+
|
|
189
|
+
Individual format and codec support still depends on the player.
|
|
190
|
+
|
|
191
|
+
## Development
|
|
192
|
+
|
|
193
|
+
Run the current source checkout:
|
|
194
|
+
|
|
195
|
+
```console
|
|
196
|
+
uvx --refresh --from . dlna-here
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Run the test and build checks:
|
|
200
|
+
|
|
201
|
+
```console
|
|
202
|
+
uv run python -m unittest discover -s tests -v
|
|
203
|
+
uvx ruff check .
|
|
204
|
+
uvx ruff format --check .
|
|
205
|
+
uv build
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
See
|
|
209
|
+
[ROADMAP.md](https://github.com/pseudosavant/dlna-here/blob/main/ROADMAP.md)
|
|
210
|
+
for project status and permanent non-goals.
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
`dlna-here` is licensed under the
|
|
215
|
+
[MIT License](https://github.com/pseudosavant/dlna-here/blob/main/LICENSE).
|
|
216
|
+
|
|
217
|
+
Copyright (c) 2026 John Paul Ellis
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# dlna-here
|
|
2
|
+
|
|
3
|
+
`dlna-here` is a small command-line UPnP/DLNA media server for sharing a local
|
|
4
|
+
folder—or one media file—on your local network. It serves videos, audio, and
|
|
5
|
+
images exactly as they are, with no transcoding and no persistent media
|
|
6
|
+
database.
|
|
7
|
+
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
Once [`uv`](https://docs.astral.sh/uv/) is installed, open a terminal in any
|
|
11
|
+
media folder and run:
|
|
12
|
+
|
|
13
|
+
```console
|
|
14
|
+
uvx dlna-here
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That is it. There is no separate `dlna-here` installation or configuration
|
|
18
|
+
step. Open the UPnP/DLNA browser on a player connected to the same network and
|
|
19
|
+
look for `<folder> on <computer>`.
|
|
20
|
+
|
|
21
|
+
Press `Q` or `q` in the terminal to stop the server. `Ctrl+C` works too. On a
|
|
22
|
+
terminal that does not support immediate single-key input, enter `q` and press
|
|
23
|
+
Enter.
|
|
24
|
+
|
|
25
|
+
You can also serve a different folder:
|
|
26
|
+
|
|
27
|
+
```console
|
|
28
|
+
uvx dlna-here "D:\Videos"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or expose one file without making its containing folder or sibling files
|
|
32
|
+
browsable:
|
|
33
|
+
|
|
34
|
+
```console
|
|
35
|
+
uvx dlna-here "D:\Videos\Movie.mp4"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## What it does
|
|
39
|
+
|
|
40
|
+
- Preserves the filesystem's folder hierarchy.
|
|
41
|
+
- Reflects additions, removals, and renames without restarting the server.
|
|
42
|
+
- Serves original files with HTTP byte-range support for playback and seeking.
|
|
43
|
+
- Uses filenames and filesystem metadata for browsing; it does not read media
|
|
44
|
+
contents before a player requests a file.
|
|
45
|
+
- Excludes hidden files and directories.
|
|
46
|
+
- Shows media names without the final filename extension by default.
|
|
47
|
+
- Follows symbolic links and Windows junctions by default, with cycle
|
|
48
|
+
protection.
|
|
49
|
+
- Supports Windows, Linux, and macOS. Windows is the primary platform.
|
|
50
|
+
|
|
51
|
+
## Command-line arguments
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
dlna-here [path] [options]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Argument | Description |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `[path]` | File or folder to serve. The current directory is used when omitted. A file path exposes only that supported media file. |
|
|
60
|
+
| `-h`, `--help` | Show the complete command help and exit. |
|
|
61
|
+
| `--interface auto\|NAME\|IPv4` | Select the IPv4 network interface. `auto` is the default and prefers the interface used by the system's default route. A displayed interface name or a concrete IPv4 address can also be used. |
|
|
62
|
+
| `--port PORT` | Set the HTTP media port. The default, `0`, asks the operating system to choose an available port. |
|
|
63
|
+
| `--name NAME` | Set the friendly server name displayed by media players. The default is `<file-or-folder> on <computer>`. |
|
|
64
|
+
| `--show-extensions` | Show full filenames such as `Movie.mp4` instead of `Movie`. This also affects the automatic server name in single-file mode. Folder names are never changed. |
|
|
65
|
+
| `--no-follow-links` | Omit symbolic links and Windows junctions. By default they are followed, including links whose targets are outside the served folder. |
|
|
66
|
+
| `--list-interfaces` | List the IPv4 interfaces available for `--interface` and exit. |
|
|
67
|
+
| `--verbose` | Print protocol, filesystem, request, and transfer diagnostics. |
|
|
68
|
+
| `--version` | Print the `dlna-here` version and exit. |
|
|
69
|
+
| `--about` | Print project and license information and exit. |
|
|
70
|
+
|
|
71
|
+
For example:
|
|
72
|
+
|
|
73
|
+
```console
|
|
74
|
+
uvx dlna-here "D:\Videos" --name "Living room videos"
|
|
75
|
+
uvx dlna-here --list-interfaces
|
|
76
|
+
uvx dlna-here --interface "Wi-Fi"
|
|
77
|
+
uvx dlna-here --interface 192.168.1.25 --port 8200
|
|
78
|
+
uvx dlna-here --show-extensions --no-follow-links
|
|
79
|
+
uvx dlna-here --verbose
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The HTTP server binds to the selected IPv4 address. UPnP discovery continues
|
|
83
|
+
to use its standard IPv4 multicast address and UDP port 1900.
|
|
84
|
+
|
|
85
|
+
## Supported media files
|
|
86
|
+
|
|
87
|
+
File extensions are matched case-insensitively.
|
|
88
|
+
|
|
89
|
+
- Video: `.3g2`, `.3gp`, `.asf`, `.avi`, `.divx`, `.flv`, `.m2t`, `.m2ts`,
|
|
90
|
+
`.m4v`, `.mkv`, `.mov`, `.mp4`, `.mpe`, `.mpeg`, `.mpg`, `.mts`, `.ogm`,
|
|
91
|
+
`.ogv`, `.ts`, `.vob`, `.webm`, `.wmv`
|
|
92
|
+
- Audio: `.aac`, `.aif`, `.aiff`, `.alac`, `.flac`, `.m4a`, `.mp2`, `.mp3`,
|
|
93
|
+
`.oga`, `.ogg`, `.opus`, `.wav`, `.wave`, `.wma`
|
|
94
|
+
- Images: `.avif`, `.bmp`, `.gif`, `.heic`, `.heif`, `.jpe`, `.jpeg`, `.jpg`,
|
|
95
|
+
`.png`, `.thm`, `.tif`, `.tiff`, `.webp`
|
|
96
|
+
|
|
97
|
+
Support here means that `dlna-here` recognizes the extension, advertises an
|
|
98
|
+
appropriate media type, and serves the original bytes. It does not inspect or
|
|
99
|
+
convert the container, video codec, audio codec, or image encoding. The
|
|
100
|
+
receiving player must support the actual file.
|
|
101
|
+
|
|
102
|
+
## Sidecar thumbnails and artwork
|
|
103
|
+
|
|
104
|
+
`dlna-here` can advertise existing `.jpg`, `.jpeg`, `.png`, or `.thm` images
|
|
105
|
+
as sidecar artwork for video and audio without opening the media file.
|
|
106
|
+
|
|
107
|
+
The simplest arrangement uses matching filenames:
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
Movies/
|
|
111
|
+
├── Movie.mp4
|
|
112
|
+
└── Movie.jpg
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Artwork is selected in this order:
|
|
116
|
+
|
|
117
|
+
1. Same-stem artwork such as `Movie.jpg`
|
|
118
|
+
2. Item-specific artwork such as `Movie-poster.png` or `Movie.poster.jpg`
|
|
119
|
+
3. Folder artwork named `cover`, `folder`, `album`, or `poster`
|
|
120
|
+
|
|
121
|
+
The original artwork file is served without resizing or converting it.
|
|
122
|
+
Artwork selected for a video is hidden from the ordinary image listing.
|
|
123
|
+
Single-file mode can use sidecar artwork from the selected file's directory
|
|
124
|
+
without exposing any other files there.
|
|
125
|
+
|
|
126
|
+
To create missing same-stem JPEG thumbnails recursively for a folder of
|
|
127
|
+
videos, use the helper script from
|
|
128
|
+
[`player.html`](https://github.com/pseudosavant/player.html). It requires
|
|
129
|
+
`ffmpeg` on `PATH`, skips videos that already have a thumbnail, and does not
|
|
130
|
+
overwrite existing artwork:
|
|
131
|
+
|
|
132
|
+
```console
|
|
133
|
+
uv run https://raw.githubusercontent.com/pseudosavant/player.html/main/prerender-video-thumbnails.py "D:\Videos"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## What it deliberately does not do
|
|
137
|
+
|
|
138
|
+
- It does not transcode video or audio.
|
|
139
|
+
- It does not remux media or mux subtitles into a stream.
|
|
140
|
+
- It does not expose or manage external subtitle files.
|
|
141
|
+
- It does not build or maintain a persistent media database.
|
|
142
|
+
- It does not have a web interface, user accounts, or authentication.
|
|
143
|
+
- It does not have persistent configuration; each run is controlled by
|
|
144
|
+
command-line arguments.
|
|
145
|
+
|
|
146
|
+
The catalog is built from the live filesystem as players browse it. Clients
|
|
147
|
+
receive the original file and decide whether they can play it.
|
|
148
|
+
|
|
149
|
+
## Network security
|
|
150
|
+
|
|
151
|
+
`dlna-here` is an unauthenticated server intended for a trusted local network.
|
|
152
|
+
Anyone who can reach it can browse and request the files it exposes. Do not
|
|
153
|
+
publish its HTTP port to the Internet.
|
|
154
|
+
|
|
155
|
+
## Tested players
|
|
156
|
+
|
|
157
|
+
`dlna-here` has been tested with:
|
|
158
|
+
|
|
159
|
+
- VLC
|
|
160
|
+
- Windows Media Player Legacy (`wmplayer.exe`)
|
|
161
|
+
- Roku Media Player
|
|
162
|
+
- Xbox Media Player
|
|
163
|
+
|
|
164
|
+
Individual format and codec support still depends on the player.
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
Run the current source checkout:
|
|
169
|
+
|
|
170
|
+
```console
|
|
171
|
+
uvx --refresh --from . dlna-here
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Run the test and build checks:
|
|
175
|
+
|
|
176
|
+
```console
|
|
177
|
+
uv run python -m unittest discover -s tests -v
|
|
178
|
+
uvx ruff check .
|
|
179
|
+
uvx ruff format --check .
|
|
180
|
+
uv build
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
See
|
|
184
|
+
[ROADMAP.md](https://github.com/pseudosavant/dlna-here/blob/main/ROADMAP.md)
|
|
185
|
+
for project status and permanent non-goals.
|
|
186
|
+
|
|
187
|
+
## License
|
|
188
|
+
|
|
189
|
+
`dlna-here` is licensed under the
|
|
190
|
+
[MIT License](https://github.com/pseudosavant/dlna-here/blob/main/LICENSE).
|
|
191
|
+
|
|
192
|
+
Copyright (c) 2026 John Paul Ellis
|