Termux DLNA Server
Runs on your phone, serves Movies/TV Shows from an SMB share to Kodi over DLNA,
transcoding to h264/AAC 720p via h264_mediacodec only when the source file
needs it.
Files
config.py— edit this first. SMB creds, paths, ports, UUID.smb_layer.py— SMB access with reconnect-on-failure wrapper.db.py— SQLite library (Movies / TV Shows / Season / Episode tree).scanner.py— walks the SMB share, populates the DB, periodic rescan with mark-and-sweep.transcode.py— ffprobe inspection, transcode decision, ffmpeg command building, forced-subtitle extraction.cds.py— DLNA ContentDirectory service (Browse / DIDL-Lite XML).ssdp.py— SSDP discovery (multicast NOTIFY + M-SEARCH response).server.py— HTTP server tying it together: description.xml, SOAP control, raw SMB proxy, stream endpoint, subtitle endpoint.main.py— entry point.
Setup
pkg install ffmpeg python
pip install -r requirements.txt
Edit
config.py— at minimum:SMB_SERVER,SMB_SHARE,SMB_USERNAME,SMB_PASSWORD,SMB_MOVIES_PATH,SMB_TVSHOWS_PATH. Generate a real UUID forUUID(e.g.python3 -c "import uuid; print(uuid.uuid4())") and keep it stable — Kodi uses it to identify the server across restarts.Confirm the hardware encoder exists before relying on transcoding:
ffmpeg -encoders | grep mediacodecIf nothing prints,
h264_mediacodecisn’t in your ffmpeg build. Direct-stream playback (files already h264/AAC/720p/mp4-or-mkv) still works fine without it — only transcoding is affected.main.pylogs an error at startup if it’s missing but does not refuse to start, since direct-stream is still useful on its own.Run it:
python3 main.pyFirst run does a full library scan before the server starts listening, so expect a delay proportional to your library size and SMB latency.
On Kodi (RPi3): Settings → Media → check “Show unavailable added-on files source” isn’t relevant — instead add a UPnP source, or it should just appear under Videos → Add-ons → UPnP/DLNA devices automatically once SSDP announces.
How playback decisions work
needs_transcode()intranscode.pychecks: video codec h264, audio codec AAC (English track preferred, falls back to first audio track), height <=720, container mp4/mkv. All true → direct stream (_serve_raw, byte-range passthrough, no ffmpeg involved). Any false → transcode (build_transcode_cmd, piped fragmented MP4 viah264_mediacodec).- Forced subtitles (disposition flag, not full subtitle tracks) are extracted
to a sidecar
.srtand advertised viasec:CaptionInfoExin the DIDL-Lite item. Kodi fetches and auto-loads it. This is independent of the transcode decision — happens for both direct and transcoded playback.
Known gaps / things that will bite you
sec:CaptionInfoExsupport has shifted across Kodi versions and isn’t formally documented outside Samsung’s own DLNA guidelines. Test against your actual Kodi version. If it doesn’t auto-load, fall back tosec:CaptionInfo(older tag) or a second plain<res>withprotocolInfo="http-get:*:text/srt:*".- Files with no
forceddisposition flag (common in scene rips with stripped metadata) won’t get a subtitle even if they should. There’s no reliable automatic fix for this — the code has no per-file manual-override hook yet. Add one (e.g. a JSON file mapping smb_path → subtitle stream index) if this turns out to matter in practice. - SMB session drops mid-stream (phone sleep, wifi radio doze) will kill an
in-progress
_serve_rawor transcode read with no retry — the retry wrapper insmb_layer.pyis only used for scan operations (scandir/stat), not for the open file handle used during streaming, since retrying mid-stream means re-seeking and the current code doesn’t do that. If streams die randomly, this is the first place to look. - The
_serve_streamtranscode path calls ffmpeg withPopenand no timeout or process-limit tracking. If a client opens a stream and never reads it to completion (e.g. a seek that abandons the old connection), the ffmpeg process may keep running as an orphan. Worth adding process cleanup onBrokenPipeError/socket close if you see zombie ffmpeg processes accumulate. - Movie-per-folder scan takes the first video file found in a movie folder and ignores the rest — fine for one file per folder, silently wrong if a folder has extras/samples alongside the real file.
- No auth on the HTTP server. Anything on your LAN that can reach the phone’s IP can browse and stream your library. Fine on a home network, not something to expose past your router.
Testing checklist (in order)
ffmpeg -encoders | grep mediacodec— confirms transcode path is even possible.- Run
main.py, check log for “SSDP listening” and “HTTP server listening”. curl http://<phone-ip>:8200/description.xmlfrom another device — confirms HTTP + description are reachable.- Check Kodi finds it under UPnP devices — confirms SSDP is working across the LAN (multicast can be blocked by some router/AP configs, this is the most likely first failure point).
- Browse into Movies/TV Shows in Kodi — confirms SOAP Browse + SQLite library.
- Play a file you know is already h264/AAC/720p — confirms direct-stream path, no ffmpeg involved.
- Play a file that needs transcoding — confirms ffmpeg pipe path end-to-end.
- Play a file with foreign dialogue you expect a forced sub on — confirms CaptionInfoEx + Kodi’s handling of it.