ptp-proxy IPTV · files · PTP nodes

ptp-proxy — IPTV, files and media processing IPTV, storage and managed media node

ptp-proxy combines IPTV delivery, shared restreaming, transcoding, VOD tunnelling, recordings and secure publication of local or remote files in one service. It can run by itself or connect to a PTP server as a managed node.

What is it for?
  • Hide IPTV-provider and file-origin credentials
  • Publish HLS channels behind your own URLs
  • Share one restream connection among several players
  • Apply software or hardware transcoding profiles
  • Tunnel VOD and downloads with byte ranges and seek
  • Serve local folders, disks and mounted drives
  • Unify WebDAV, SFTP, SMB, NFS, FTP/FTPS and HTTP catalogues
  • Connect external providers such as S3, BitTorrent, aMule, rclone, IPFS or SABnzbd
  • Prepare content on demand through asynchronous operations
  • Schedule recordings from channels or URLs
  • Run as a local or remote PTP-managed node
  • Run as a Windows Service or Linux daemon
How does it work?

Clients use ptp-proxy URLs for channels, VOD or files. The server validates identity and permissions, contacts the configured origin, transforms media when required and returns a uniform response without revealing origin secrets.

What it is NOT
  • It does not include third-party channels, films, series or files
  • It does not discover services or credentials automatically: the administrator adds authorised sources
  • It does not replace a VPN: it may use an existing network or tunnel but does not create one
  • It does not make addon content public: every provider remains subject to permissions, limits and source policy

Operating modes

ModeWhat it solvesData flowFFmpeg
HLS proxyRewrites playlists, segments and keys without re-encoding video.Each client keeps its own session with the origin.No
RestreamCopies or transcodes a channel and distributes one shared output.One channel session can serve several clients.Yes
VOD tunnelDelivers remote video files with size, download and seek support.Each viewer receives an independent byte-range stream.No
File serverPublishes local, mounted or remote files through one API.Client → ptp-proxy → storage source.No
On-demand preparationStarts a remote job and publishes the result when it becomes available.Client → operation → external provider → ready object.Optional
PTP nodeExposes sources, profiles and operations to a central PTP server.The node initiates control; delivery may be direct or relayed.Task-dependent

What you can build with ptp-proxy

The same features can be combined. One machine may simultaneously act as an IPTV proxy, file server, transcoding edge and remote PTP agent.

Private IPTV gateway

Publish playlists and streams with your own URLs and access credentials.

  • HLS proxy
  • HMAC claims
  • Private headers
  • Optional CONNECT
Shared restream

Reduce origin connections and adapt codec or bitrate to client devices.

  • JSON profiles
  • CPU or GPU
  • disk, memory_fs or pipe_ram output
  • Recordings
Media file server

Turn folders and remote storage into seekable playback URLs.

  • Local and mounted drives
  • Remote protocols
  • HTTP ranges
  • Temporary links
Content preparation

Control slow jobs and publish a file only after it is ready.

  • BitTorrent
  • aMule/eD2k
  • IPFS
  • SABnzbd
PTP edge node

Keep credentials and files near the origin while PTP coordinates the catalogue.

  • Outbound connection
  • Source inventory
  • Available profiles
  • Direct or relayed delivery
Extensible connector

Add providers through plugins without changing the client-facing API.

  • SDK and example
  • Validated manifests
  • Integrity checks
  • Administrative status and restart

Main roles of a node

RoleTypical inputClient outputTypical use
IPTV channelsProvider playlist or streamOwn HLS URL or restreamLive TV
VODRemote URL or claim tokenRange stream or downloadFilms and episodes
StorageFolder, remote protocol or catalogueHTTP listing and objectsFile library
External providerSpecialised serviceReady object through common APIS3, P2P, cloud
RecordingChannel or URLFile created on the nodeProgrammes and events
PTP nodeJobs from the central serverInventory, results and deliveryDistributed installations

Deployment models

DeploymentManaged byConnectivityChoose it when
StandaloneIts own config.jsonClients connect directly to the proxySingle machine or autonomous use
Local beside PTPPTP starts and supervises the processLoopback or LANSimple combined installation
Remote PTP nodeCentral PTP over outbound HTTPSWorks behind NAT or firewallNAS, remote home, VPS or branch
Controlled public gatewayNode administratorTLS, tokens and limits requiredDirect delivery to external clients
Quick path by goal
  1. IPTV only: configure channels and test /playlist.m3u.
  2. File server: add storage.sources and test /api/storage.
  3. Transcoding: install profiles, configure FFmpeg and choose profile.
  4. On-demand content: enable a plugin source with allow_prepare.
  5. PTP integration: enable managed_node or let PTP start the local node.

System requirements

Windows
Windows 10 / 11 (64-bit)
  • FFmpeg — ffmpeg.exe executable in PATH or in the same folder
  • Visual C++ Redistributable 2022 (usually already installed)
  • Without GPU: CPU only, no additional drivers
  • With GPU: NVIDIA driver ≥ 570.0
Linux
Ubuntu 20.04+ / Debian 11+
  • FFmpeg — sudo apt install ffmpeg
  • glibc 2.31+ (included in Ubuntu 20.04 and later)
  • Without GPU: CPU only, no additional drivers
  • With GPU: NVIDIA driver + CUDA installed
FFmpeg is not required for HLS proxying, VOD tunnelling, file serving, remote listings or providers that already return a finished object. It is required only for restreaming, transcoding and some recording workflows.

Dependencies by feature

Install only what your deployment needs. The main binary can serve channels and native sources without Python or addon tools.

FeatureRequiredOptionalNotes
HLS proxy and VODptp-proxy and network accessOwn TLS endpointNo FFmpeg when no re-encoding is required.
Restream, transcoding and recordingFFmpegGPU and driversThe selected profile must exist on the node.
Local or remote file serverAccess to the sourcePrivate CA or mountFFmpeg is not required to serve the file.
Python pluginsPython 3Provider-specific dependencyBundled S3 and addons run outside the core.
PTP-managed nodeOutbound HTTPS access to PTPDirect public URLCan work behind NAT and use relay as fallback.
memory_fsPrepared memory-backed filesystemSystem limitsptp-proxy does not mount it automatically.
Optional addon tools
  • aMule/aMuled for the eD2k provider.
  • rclone for administrator-configured cloud remotes.
  • Kubo for IPFS/IPNS roots.
  • SABnzbd for NZB jobs.
  • aria2c when using the bundled BitTorrent provider.
  • Optional dependencies are not required unless that provider is configured.
Connectivity and ports
  • The default HTTP listener is 127.0.0.1:8180.
  • For remote access, configure TLS or a secure reverse proxy and an access key.
  • A managed node initiates its connection to PTP and does not need to expose its administrative API to the Internet.
  • Remote sources must be reachable from the machine running ptp-proxy.
  • Addon control APIs should remain on loopback whenever possible.

Important notes about FFmpeg

FFmpeg is required if you use channels in restream mode. If you only use proxy mode or VOD tunnel, FFmpeg is not needed, but having it available is recommended.

Install FFmpeg on Windows

  1. Go to gyan.dev/ffmpeg/builds and download the release-full build
  2. Extract to e.g. C:\ffmpeg\
  3. Add C:\ffmpeg\bin to the system PATH or copy ffmpeg.exe to the same folder as ptp-proxy.exe
  4. Verify: open a terminal and run ffmpeg -version

Install FFmpeg on Linux / WSL2

sudo apt update && sudo apt install ffmpeg
# Verify:
ffmpeg -version

Which FFmpeg version do I need?

FeatureMinimum FFmpeg version
Copy / proxy / basic HLS4.0+
CPU transcoding (libx264)4.0+ with libx264 compiled in
NVIDIA GPU (h264_nvenc)4.4+ with NVENC support + driver ≥ 570.0
All of the above (recommended)6.0 or later

Verify NVENC support (NVIDIA GPU)

# Should show "h264_nvenc" in the list
ffmpeg -hide_banner -encoders | grep nvenc
# Quick test — if it errors, the driver is not compatible
ffmpeg -hide_banner -f lavfi -i nullsrc -t 1 -c:v h264_nvenc -f null -

Architecture — connection flows

The first diagrams explain IPTV and restreaming. Additional flows show how ptp-proxy publishes files, coordinates asynchronous providers and works as a PTP-managed node.

Legend: Control / signaling Video stream Web content (images, EPG)

1. Direct connection — no proxy

DVB-T Media server server Other… IPTV Provider Cloudflare tunnel ⚠ credentials exposed IPTV Web Browser client App / VLC client ctrl video epg

Users connect directly to the IPTV provider. Every client opens its own set of connections (control, video, EPG). Provider credentials travel in every request and are visible to the client.

2. ptp-proxy inserted

IPTV Provider ptp-proxy ✓ credentials hidden ⚙ per-flow optional Browser client App / VLC client 2×

ptp-proxy sits between clients and the provider. Clients only see the proxy address. Credentials never leave the proxy. Each client still generates its own upstream connection (proxy mode). Dashed lines indicate flows that the server can individually enable or disable — control, video and web-player traffic can each be proxied or bypassed independently.

3. Restream mode — shared upstream

IPTV Provider ptp-proxy FFmpeg 1 connection upstream Client 1 Client 2 Client 3 ← N clients 1×

In restream mode, FFmpeg opens one upstream connection per channel. All clients receive the re-distributed stream. The provider sees exactly one connection regardless of how many viewers there are.

4. VPN scenario — proxy inside a tunnel

IPTV Provider VPN / tunnel ptp-proxy ↑ VPN IP Browser client App / VLC client

If ptp-proxy runs on a host that sits inside a VPN (or a Cloudflare tunnel, WireGuard, etc.). All upstream traffic to the provider exits through the VPN IP. Clients connect to ptp-proxy normally from the outside.

5. Storage gateway

VLC / application / PTP
↓ lists or requests an object
ptp-proxy — permissions, limits and temporary link
↓
Local / NAS / WebDAV / SFTP / SMB / NFS / FTP / HTTP / plugin

The client uses the same API without knowing where the file lives. ptp-proxy keeps credentials, publishes metadata and translates player seek into origin ranges.

6. PTP-managed node

remote ptp-proxy → outbound HTTPS → PTP
PTP → inventory, browsing and jobs → node
node → results, profiles and operations → PTP
player → direct node or player → PTP → relay → node

PTP coordinates multiple nodes without treating them as generic tunnel proxies. Each node keeps its sources and plugins while PTP owns users, catalogue and location selection.

7. Asynchronous plugin preparation

client / PTP → POST prepare
ptp-proxy → external provider
queued → preparing → ready
ready result → /storage/<source>/<path>
client → normal Range playback

The operation controls content that is not yet a playable object. When it completes, the result uses the same API and permissions as any other source.

Responsibility boundaries

ComponentPrimary responsibility
ClientRequests playlists, objects, ranges or authorised operations.
ptp-proxyOrigin access, local security, streaming, transcoding and plugins.
PTPUsers, catalogue, library, node coordination and delivery choice.
External providerConnects to a specialised service and publishes the result.
FFmpegCopies, transcodes or records when a workflow requires it.

Installation on Windows

Installation = copy one file. No installer, no registry, no system dependencies to install.
  1. Download ptp-proxy.exe from the Downloads section of this guide
  2. Create a folder, e.g. C:\ptp-proxy\
  3. Copy ptp-proxy.exe into that folder
  4. Copy config.example.json to the same folder and rename it to config.json
  5. Edit config.json with a text editor (Notepad++, VS Code...)
  6. Open a terminal (cmd or PowerShell) in that folder and run:
ptp-proxy.exe --config config.json
If you see the message Listening on 0.0.0.0:8180 in the console (or in the log), the proxy is running.

Run automatically on startup (optional)

To have the proxy start automatically with Windows, use the built-in service support (recommended — no extra tools needed) or Task Scheduler / NSSM (alternative).

Install as a Windows Service (recommended)

Requires a Command Prompt or PowerShell running as Administrator. The service starts automatically with Windows and can be managed via sc or the Services panel.
# Install and start the service
ptp-proxy.exe --install-service --config "C:\ptp-proxy\config.json"
ptp-proxy.exe --start-service
sc query ptp-proxy

# Stop and uninstall the service
ptp-proxy.exe --stop-service
ptp-proxy.exe --uninstall-service

By default the service is installed with automatic startup. If you prefer to start it manually, add --demand:

# Manual start (only when explicitly requested)
ptp-proxy.exe --install-service --demand --config "C:\ptp-proxy\config.json"

Alternative: NSSM

NSSM is still valid if you already use it for other services.

# NSSM (https://nssm.cc)
nssm install PtpProxy "C:\ptp-proxy\ptp-proxy.exe" "--config C:\ptp-proxy\config.json"
nssm start PtpProxy

Windows vs Linux differences

AspectWindowsLinux
Binaryptp-proxy.exeptp-proxy (no extension)
Paths in config.jsonC:/streams/ or ./streams//home/user/streams/ or ./streams/
NVIDIA GPUYes, with Windows driverYes, with Linux driver + CUDA
Job Object (kill child processes)Automatic — FFmpeg is killed when the proxy closesSIGTERM signal propagated
Path separator/ or \ (both valid in config.json)/ only
FFmpeg in PATHAdd C:\ffmpeg\bin to PATH or set "path": "C:/ffmpeg/bin/ffmpeg.exe"Usually already in PATH after apt install

Installation on Linux

  1. Download the binary ptp-proxy (Linux x64) to e.g. /opt/ptp-proxy/
  2. Grant execute permission:
chmod +x /opt/ptp-proxy/ptp-proxy
  1. Copy config.example.json as config.json in the same folder and edit it
  2. Run:
cd /opt/ptp-proxy
./ptp-proxy --config config.json

Run as a systemd service (recommended on Linux)

Create the file /etc/systemd/system/ptp-proxy.service

[Unit]
Description=ptp-proxy
After=network.target

[Service]
ExecStart=/opt/ptp-proxy/ptp-proxy --config /opt/ptp-proxy/config.json
WorkingDirectory=/opt/ptp-proxy
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now ptp-proxy
sudo systemctl status ptp-proxy
journalctl -u ptp-proxy -f

The binary is static (no extra dependencies)

The Linux binary includes OpenSSL compiled statically. It only needs glibc (included in any modern Ubuntu/Debian) and FFmpeg on the system for restream channels.

Using the proxy on WSL2 (Linux inside Windows)

Why WSL2? The main use case is routing the proxy's traffic through a Linux VPN. If the VPN is inside the Linux VM, all proxy traffic exits through that interface, even if the client (web app, VLC) is on Windows.

How WSL2 networking works

WSL2 has its own network interface, but Windows includes a mechanism called localhost forwarding: if an application inside WSL2 listens on 127.0.0.1, Windows automatically redirects connections to the host's 127.0.0.1 to the VM.

Windows 127.0.0.1:8180 ──→ (automatic localhost forwarding) ──→ WSL2 Ubuntu 127.0.0.1:8180
The proxy listens inside WSL2 Linux → Windows sees it at its own 127.0.0.1
All outgoing proxy traffic uses the Linux/VPN interface, not Windows'

Step 1 — Install WSL2 and Ubuntu

wsl --install
wsl --install -d Ubuntu-24.04

Step 2 — Copy the binary to WSL2

wsl -d Ubuntu-24.04 -- bash -c "cp /mnt/c/path/ptp-proxy /home/user/ptp-proxy"
wsl -d Ubuntu-24.04 -- bash -c "chmod +x /home/user/ptp-proxy"

Or access WSL2 directly and move the files:

wsl
cp /mnt/c/path/to/binary/ptp-proxy ~/ptp-proxy
chmod +x ~/ptp-proxy

Step 3 — Configure config.json for WSL2

The key is to make the proxy listen on 127.0.0.1 inside WSL2. Windows handles the forwarding automatically.

"server": {
  "listen": "127.0.0.1",   // ← IMPORTANT: not 0.0.0.0
  "port":   8180
}
Do not use 0.0.0.0 in WSL2 if the goal is for only Windows to access the proxy via localhost forwarding. With 127.0.0.1, the proxy is only reachable from the host itself (Windows included via forwarding), not from the local network.

Step 4 — Start the proxy from PowerShell

wsl -d Ubuntu-24.04 -- bash -c "cd /path/to/proxy ; nohup ./ptp-proxy > ptp-proxy.log 2>&1 &"
wsl -d Ubuntu-24.04 -- bash -c "tail -5 /path/to/proxy/ptp-proxy.log"
wsl -d Ubuntu-24.04 -- bash -c "pkill ptp-proxy"

Step 5 — Verify from Windows

Invoke-WebRequest http://127.0.0.1:8180/health
curl http://127.0.0.1:8180/health
If you see ok as a response, the proxy in WSL2 is running and is accessible from Windows.

Paths in config.json for WSL2

You can put the streams folder directly in Windows to easily see the files:

"storage_path": "/mnt/c/path/streams/"   // Windows folder accessible from WSL2

FFmpeg in WSL2

sudo apt update && sudo apt install ffmpeg
WSL2 FFmpeg cannot use the Windows NVIDIA GPU unless you have configured CUDA for WSL2. For most cases, use CPU transcoding (libx264) in WSL2.

Basic configuration (config.json)

The config.json file is the only configuration file. Edit it with any text editor. Each block is explained below.

Configuration map

The file is split by responsibility. Start with server, security and one source or channel, then add the remaining blocks as needed.

BlockControlsNeeded when
serverListener, TLS and HTTP limitsAlways
securityKeys, management IPs, SSRF and rate limitingAlways
ffmpegExecutable path and HLS timingRestream, transcode or record
transcodingProfile directory and default profileProfiles are used
restreamHLS output, directory and lifetimeRestream channels
storageSources, permissions, tokens, plugins and limitsFile server or addons
coreDynamic claims from a validation serverPTP or another core issues tokens
managed_nodeEnrollment and outbound connection to PTPThe proxy is a managed node
channelsStatic proxy/restream channelsThey do not depend on a dynamic core
recordingsRecording directory and policyThe recording API is used

Full structure with default values

{
  // ── Server ──────────────────────────────────────────────
  "server": {
    "listen": "127.0.0.1",
    "port":   8180,
    "worker_threads": 0,
    "request_header_limit": 32768,
    "request_body_limit": 1048576,
    "request_timeout_sec": 30,
    "write_timeout_sec": 60,
    "keep_alive_timeout_sec": 30,
    "max_requests_per_connection": 100,
    "health_secret": "",
    "tls": {
      "enabled": false,
      "port":    8443,
      "cert":    "",
      "key":     ""
    }
  },
  // ── Logging ───────────────────────────────────────────────
  "logging": {
    "file": "ptp-proxy.log",
    "json": true
  },
  // ── Security ─────────────────────────────────────────────
  "security": {
    "block_private_ips": true,
    "management_ips":   ["127.0.0.1", "::1"],
    "access_key":       "",
    "localhost_bypass": true,
    "proxy_raw_public": false,
    "key_exempt_paths": [],
    "allow_unauthenticated_remote": false,
    "allow_access_key_query": false,
    "allow_legacy_url_query": false,
    "verify_upstream_tls": false,
    "upstream_ca_file": "",
    "rate_limit": {
      "enabled":     true,
      "window_sec":  10,
      "max_requests": 300
    }
  },
  // ── FFmpeg ─────────────────────────────────────────────────
  "ffmpeg": {
    "path":             "ffmpeg",
    "hls_time_seconds": 4,
    "hls_list_size":    10,
    "extra_args":       []
  },
  // ── Transcoding profiles ───────────────────────────────────
  "transcoding": {
    "profiles_dir":            "./profiles",
    "default_profile":         "libopenh264_balanced",
    "allow_legacy_extra_args": true
  },
  // ── Restream output ────────────────────────────────────────
  "restream": {
    "output":           "disk",
    "storage_path":     "./streams/",
    "startup_grace_ms": 30000,
    "idle_timeout_sec": 120
  },
  // ── Storage sources ───────────────────────────────────────────
  "storage": {
    "sources": [
      {
        "id":        "media",
        "type":      "local",
        "root_path": "./media"
      }
    ]
  },
  // ── Core (claims integration) ─────────────────────────────────────────────────────────────
  "core": {
    "url":    "",
    "secret": ""
  },
  // ── Channels ────────────────────────────────────────────────
  "channels": []
}

<code>server.listen</code> options

ValueMeaningWhen to use
"0.0.0.0"Accepts connections on all network interfacesServer accessible from the local network or internet
"127.0.0.1"Local connections onlyWSL2 usage or behind nginx on the same server
"192.168.1.x"Connections from that network interface onlyServe only on the local network, not on the VPN

HTTP limits and capacity

The limits protect against slow connections, unbounded bodies and excessive keep-alive sessions, keeping the service stable under load.

FieldDefaultDescription
worker_threadsMaximum number of simultaneous operations handled.Available concurrency, with a minimum of 4.
request_header_limit32768Maximum request-header bytes.
request_body_limit1048576Maximum incoming body size.
request_timeout_sec30Maximum time to read a request.
write_timeout_sec60Maximum time to write a response.
keep_alive_timeout_sec30Maximum wait between keep-alive requests.
max_requests_per_connection100Maximum requests on one persistent connection.

Storage sources and public HTTP access

Local, WebDAV, SFTP, SMB, NFS, FTP/FTPS and HTTP catalog sources share scoped authorization, opaque public IDs, temporary links, limits and ranged playback.

"storage": {
  "sources": [
    {
      "id":        "media",
      "type":      "local",
      "root_path": "./media"
    }
  ]
}
Choose a source and configure only its connection details. Mounted resources can be exposed as local or through mount_path. Localhost bypass remains disabled by default.
FieldPurpose
id / public_idInternal source name and optional opaque ID exposed in public URLs.
typelocal, webdav, sftp, smb, nfs, ftp or http_manifest.
allow_list / allow_stream / allow_downloadPer-source operations allowed for non-admin principals.
admin_onlyRestricts the source to storage:admin.
access_tokensBearer tokens with storage permissions and optional internal source IDs.
link_secretHMAC secret for exact-path temporary playback/download links.
read_buffer_bytesBackpressure buffer between 16 KiB and 4 MiB.
max_concurrent_reads*Global, per-IP and per-source transfer limits.
idle_timeout_sec / initial_retry_countNo-progress timeout and retries allowed only before response headers.
root_path / base_url / mount_pathLocal root, remote root, HTTP catalog or an already mounted SMB/NFS resource.
verify_tls / ca_fileCertificate verification for TLS connections.
known_hosts / host_key_sha256SFTP SSH host verification.
domain / require_signing / require_encryptionSMB authentication and transport policy.
connect_timeout_sec / transfer_timeout_secPositive remote connection and total transfer timeouts.

TLS / HTTPS on the listener

The server can listen on HTTP and HTTPS simultaneously (two independent ports). TLS 1.2 and TLS 1.3 are supported; SSLv2 / SSLv3 are always disabled.

"server": {
  "listen": "0.0.0.0",
  "port":   8180,
  "tls": {
    "enabled": true,
    "port":    8443,
    "cert":    "/etc/letsencrypt/live/mydomain.com/fullchain.pem",
    "key":     "/etc/letsencrypt/live/mydomain.com/privkey.pem"
  }
}
FieldDefaultDescription
enabledfalseActivates the HTTPS listener. HTTP continues working on server.port.
port8443HTTPS port. Clients must use https://host:8443/… when TLS is active.
cert""Path to the PEM file with the full certificate chain (e.g. fullchain.pem).
key""Path to the PEM file with the private key (e.g. privkey.pem).
Self-signed certificate for local / LAN testing — clients will show a certificate warning; add a browser exception or use only internally. Generate with:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"
Then set "cert": "./cert.pem" and "key": "./key.pem" in the config.
Free certificate with Let’s Encrypt (requires a public domain and port 80 open):
certbot certonly --standalone -d mydomain.com
Files: /etc/letsencrypt/live/mydomain.com/fullchain.pem and privkey.pem. Renew automatically with certbot renew.
If the proxy sits behind nginx / Caddy / Traefik, keep TLS disabled here and let the reverse proxy terminate HTTPS. Native TLS is most useful when the binary is exposed directly to the internet.

HLS output: disk, memory filesystem or integrated RAM

Memory
"output": "memory_fs" — recommended in-memory mode

Segments are written to a memory-backed directory such as tmpfs or a RAM drive. Normal HLS behaviour is retained and configuration stays consistent across PTP and ptp-proxy.

Compatibility: pipe_ram keeps the integrated segmenter for small workloads; ram remains its legacy alias.

Disk
"output": "disk"

Segments are stored under storage_path on a regular filesystem. This is the simplest option and may retain segments across a process restart.

The transcoding profile is selected independently with transcoding.default_profile or channels[].profile.

PTP-compatible transcoding profiles

ptp-proxy loads JSON profiles using the same format as PTP. Each profile has a stable identifier, labels, codec metadata and variants for direct playback, remuxing and HLS.

PTP sends only the profile identifier and profile_mode (hlstrans or hlscopy). ptp-proxy validates both and never accepts arbitrary execution arguments from the core.

Catalogue and default profile

Configure the profile directory and a default profile. Official packages include software and hardware-accelerated options.

"transcoding": {
  "profiles_dir":            "./profiles",
  "default_profile":         "libopenh264_balanced",
  "allow_legacy_extra_args": true
}
ffmpeg.extra_args and channels[].extra_args remain only for migration and can be disabled with allow_legacy_extra_args=false.

Per-channel profile and HLS variant

{
  "id":      "news",
  "mode":    "restream",
  "url":     "https://origin.example/live.m3u8",
  "profile":      "h264_nvenc_web",
  "profile_mode": "hlstrans"
}
curl -H "Authorization: Bearer ACCESS_KEY" http://127.0.0.1:8180/api/transcode/profiles

What a profile defines

FieldPublic purpose
idStable identifier shared with PTP.
labelLocalised name for user interfaces.
codecOutput family seen by the client.
mse_testBrowser compatibility probe.
hlstransHLS variant with transcoding.
hlscopyHLS variant without re-encoding.
direct / remuxVariants reserved for other playback workflows.

How the profile is selected

  • A channel may set profile and profile_mode.
  • A dynamic claim may request a profile by identifier.
  • When omitted, transcoding.default_profile is used.
  • PTP queries /api/transcode/profiles and only offers profiles available on the node.
  • Legacy arguments may be disabled after migration is complete.

Where HLS output is generated

The profile defines encoding. The output mode defines where segments are stored; these are independent choices.

ModeWhat it doesAdvantagesRecommended use
diskWrites playlists and segments to a normal filesystem.Simple, inspectable and persistent until cleaned.General use and fast disks.
memory_fsWrites normal HLS to a prepared tmpfs or RAM drive.Keeps full HLS behaviour with low I/O latency.Recommended option when RAM-backed output is wanted.
pipe_ramKeeps MPEG-TS segments in process memory.Does not need a temporary filesystem.Small, controlled workloads.
ramLegacy alias for pipe_ram.Configuration compatibility.Migrate to an explicit name.
"restream": {
  "output": "memory_fs",
  "storage_path": "/dev/shm/ptp-proxy/streams"
}
memory_fs does not create or mount a RAM drive. Point restream.storage_path at an already prepared path and enforce its size in the operating system.

Actual profile availability

A profile file does not guarantee that the encoder or driver exists on the machine. Test every profile on each node and let PTP store the availability announced by that node.

Hardware-accelerated profile

Hardware encoding can reduce CPU use. Actual capacity depends on the host, driver, resolution and selected profile.

Before selecting the profile

  • Confirm that the node exposes a compatible encoder
  • Test the profile with a short restream before assigning it in PTP
  • Keep a software profile available as a fallback choice

Included NVIDIA profile

"profile": "h264_nvenc_web"

Selection from PTP

PTP can query /api/transcode/profiles and offer only profiles installed on the selected node.

Included NVIDIA profiles

ProfileCompatibilityGoalRecommended use
h264_nvenc_webH.264 BaselineWeb compatibilityBrowsers and mixed devices
h264_nvencH.264 MainGeneral qualityClients supporting Main profile
When a profile cannot start, ptp-proxy reports the failure and does not silently replace it. Check encoder availability on that node or select another profile.

Software transcoding profiles

Software profiles work without a dedicated video encoder. Performance depends on the processor, resolution and number of concurrent restreams.

Recommended software profile

"profile": "libopenh264_balanced"

Included software profiles

ProfileCodecGoalRecommended use
libopenh264_fastH.264Lower loadLimited hosts or testing
libopenh264_balancedH.264BalanceRecommended general option
libopenh264_qualityH.264Higher qualityFew concurrent channels
libx264_fastH.264SpeedInstallations with GPL FFmpeg
libx264_qualityH.264QualityInstallations with GPL FFmpeg
libx265HEVCEfficiencyHEVC-compatible clients only
Run a local test before setting the default profile. PTP may choose different profiles per node without transporting execution arguments.

Channel configuration

Channel in proxy mode (simplest)

The proxy downloads the provider's playlist and rewrites the segments. Does not use FFmpeg.

"channels": [
  {
    "id":      "my_channel",
    "name":    "My Channel 1",
    "mode":    "proxy",
    "url":     "http://panel.provider.com/live/USER/PASS/123.m3u8",
    "headers": []
  }
]

Channel in restream mode (with FFmpeg)

{
  "id":         "nvenc_channel",
  "mode":       "restream",
  "url":        "http://panel.provider.com/live/USER/PASS/456.m3u8",
  "profile":      "h264_nvenc_web",
  "profile_mode": "hlstrans",
  "skip_re":      true
}

Channel with authentication headers

{
  "id":      "auth_channel",
  "mode":    "proxy",
  "url":     "https://cdn.provider.com/live/stream.m3u8",
  "headers": [
    { "key": "Authorization", "value": "Bearer TOKEN_HERE" },
    { "key": "Referer",       "value": "https://provider.com/" }
  ]
}

Once configured, the client accesses the channel at:

http://your-server:8180/channels/my_channel/playlist.m3u8

Static channels and dynamic flows

OriginConfigurationAdvantage
Static proxy channelchannels[].mode=proxySimple setup without re-encoding.
Static restream channelchannels[].mode=restreamShared output and controlled profile.
Dynamic live claimCore returns URL and live typeCredentials never reach the client.
Dynamic restream claimCore adds profile and profile_modePTP selects an installed profile without sending commands.
VOD/series claimCore returns URL and content typeRange tunnel with seek and download.
Recording/api/recordings APIStores an authorised channel or URL as a managed file.

Core integration

When core.url is configured, the client uses a short token. ptp-proxy validates it with the core and receives the real URL, content type and, where relevant, requested profile. Origin credentials are never returned to the player.

Recordings

The recording API can schedule a configured channel or authorised URL, query status and cancel the job. The result is written to the configured directory and is not automatically exposed as a storage source unless you add it.

  • Schedule by start/end time or duration.
  • Use a controlled output name and dedicated directory.
  • Check free space and permissions before long jobs.
  • Recordings and restreams have separate metrics.
Do not publish examples with real credentials. In production use claims, private headers or protected sources and always review redacted logs.

PTP-managed node

ptp-proxy can act as an agent of a central PTP server. The node keeps physical access to channels, files, profiles and plugins while PTP coordinates users, catalogue, browsing and playback.

The node initiates the control connection. It can therefore run behind NAT or a firewall without publishing its administrative API to the Internet.

Three ways to use the same binary

ModeConfigurationControlDelivery
Standalonemanaged_node.enabled=falseLocal configClients access the proxy directly.
Local PTP nodePTP starts the process and provides enrollmentCentral PTPLoopback or LAN.
Remote nodeProxy enrolls with a one-time tokenCentral PTP over outbound HTTPSDirect, relay or both.
Managed flow
ptp-proxy → one-time enrollment → PTP
ptp-proxy → heartbeat, sources, profiles and plugins → PTP
PTP → authorised jobs → ptp-proxy
ptp-proxy → typed results and operations → PTP
player → direct node URL or player → PTP → relay → node
When PTP is unavailable
  • The proxy HTTP server keeps working with its local configuration.
  • Already-started sources and plugins remain locally available.
  • The node reconnects with backoff.
  • Jobs requiring central control wait for reconnection.
  • The node credential can be revoked independently from PTP.

What PTP can manage

CapabilityResult
InventoryAvailable sources, profiles and providers.
BrowsingListings, metadata and directory walks.
CatalogueImport remote locations into the central library.
Protocol-v2 operationsPrepare, inspect and cancel on-demand content.
PluginsStatus and administrative restart of an external source.
TranscodingSelect a profile installed on the node.
Direct deliveryTemporary URL to a reachable node.
Reverse relayRange reads when the client cannot reach the node.

Node configuration

In an integrated installation PTP may provide these values through the environment. On a remote node they are stored in config.json; after enrollment the one-time token is replaced by a node-specific credential.

"managed_node": {
  "enabled": true,
  "server_url": "https://ptp.example.com",
  "node_name": "NAS home",
  "enrollment_token": "ONE_TIME_TOKEN",
  "credential_file": "./state/ptp-node.json",
  "public_url": "",
  "direct_delivery": false,
  "reverse_relay": true,
  "verify_tls": true
}

Direct delivery and relay

ModeAdvantageCost or limit
DirectVideo travels from node to client without crossing PTP.The client must reach public_url.
RelayWorks even when the node accepts no inbound connections.PTP transports bytes and consumes central bandwidth.
MixedPTP selects direct when possible and relay as fallback.Both paths must be configured and tested.
Managed-channel boundaries
  • Only known job types are accepted; there is no generic shell command.
  • Source credentials remain on the node.
  • Every job has identity, limits and a deadline.
  • Relay reads are size-bounded and range-based.
  • Plugin administration still requires explicit permissions.
Managed mode complements standalone use. It does not force every client through PTP and does not turn the control channel into a general-purpose tunnel.

Local and remote storage

ptp-proxy is also a file server and storage gateway. It publishes local folders, mounted drives, remote protocols, HTTP catalogues and external providers through the same listing, metadata and playback API.

Read-only: list, metadata, playback, ranges, seek and cancellation are supported. Upload, delete, rename and remote permission changes are not implemented.

Four ways to present content

The public API does not change whether the file is on the same machine, on a NAS, on a remote service or still needs to be prepared.

ModelOriginAvailabilityExample
Local serverFolder, disk or mounted driveImmediatetype: local
Remote gatewayWebDAV, SFTP, SMB, NFS or FTP/FTPSImmediate when the origin respondstype: webdav
HTTP catalogueJSON manifest and HTTP/HTTPS objectsAccording to the cataloguetype: http_manifest
External providerS3, P2P, cloud or download managerImmediate or after an operationtype: plugin
How a file is identified

Each object is referenced by a public source ID and a relative path. The internal ID, credentials and real origin URL remain on the server.

  • source.id: administrator-facing internal identifier.
  • source.public_id: opaque identifier used in public URLs.
  • path: validated relative path inside the source.
  • etag and modified_at: detect changes.
  • range_supported: tells the player whether seek is available.
Storage is not a media catalogue

The API describes folders and files. A PTP server may import those locations, associate them with films or episodes and maintain metadata, but ptp-proxy does not invent titles or artwork by itself.

Playback lifecycle
  1. The client lists a source or receives a path from PTP.
  2. Authorization checks permission, source policy and concurrency limits.
  3. ptp-proxy reads metadata and decides whether ranges can be served.
  4. The response uses GET or HEAD with ETag, size and MIME type.
  5. The client may request another range to seek without knowing the real origin.
Common flow
Client / VLC / IPTV application
↓ HTTP + Bearer or signed link
ptp-proxy
↓ read-only connection
Local · WebDAV · SFTP · SMB · NFS · FTP/FTPS · HTTP catalog
Shared capabilities
  • The same endpoints for every source
  • HTTP ranges, seek, ETag and Last-Modified
  • Tokens restricted by permission and source
  • Opaque public IDs and temporary HMAC links
  • Global, per-IP and per-source limits
  • Timeouts, cancellation, stable streaming and metrics

Quick start

  1. Add a source to storage.sources.
  2. Create a token with storage:list and storage:read.
  3. Request GET /api/storage.
  4. Browse with /api/storage/<public_id>/list.
  5. Play with /storage/<public_id>/path.

Sources and available modes

Choose the source matching the remote service. Resources already mounted can be exposed as a directory or drive, while external providers add new services without changing the public API.

SourceDirect connectionMounted resourceMain requirementRecommended use
LocalYes—Directory readable by the processDisks, folders and mounted drives.
WebDAVYesOptionalRead-only account and TLSNextcloud, ownCloud, DAV servers and rclone.
SFTPYesOptionalSSH host verificationFile servers accessed through SSH.
SMBPlatform dependentYesReachable share and credentialsNAS and Windows/Samba shares.
NFSPlatform dependentYesExport authorized for the hostUnix/Linux storage and NAS devices.
FTP / FTPSYesOptionalFTPS recommendedExisting FTP servers and legacy systems.
HTTP catalogYes—Versioned JSON manifestCDNs, static hosting and generated catalogs.
S3 compatibleYes—Installed provider and read-only credentialsAmazon S3, MinIO, Ceph, Wasabi and compatible services.
{
  "id": "local-media",
  "public_id": "media",
  "type": "local",
  "root_path": "D:/Media",
  "allow_list": true,
  "allow_stream": true,
  "allow_download": false
}
Use absolute paths. Prefer D:/Media on Windows and /srv/media on Linux.
{
  "id": "remote-media",
  "public_id": "cloud-a91e",
  "type": "webdav",
  "base_url": "https://dav.example.test/files/user/",
  "username": "reader",
  "password": "CHANGE_ME",
  "auth_method": "any",
  "verify_tls": true,
  "allow_list": true,
  "allow_stream": true,
  "allow_download": false
}
Keep verify_tls=true. Use ca_file for a private CA; redirects are restricted to the same origin.
{
  "id": "archive",
  "public_id": "archive-a91e",
  "type": "sftp",
  "base_url": "sftp://files.example.test/media/",
  "username": "reader",
  "private_key": "./keys/id_ed25519",
  "known_hosts": "./keys/known_hosts",
  "allow_list": true,
  "allow_stream": true
}
Configure known_hosts or host_key_sha256 to verify the server. Reserve allow_unknown_host for controlled testing.
{
  "id": "nas",
  "public_id": "nas-media",
  "type": "smb",
  "base_url": "smb://nas.example.test/video/",
  "username": "reader",
  "password": "CHANGE_ME",
  "domain": "MEDIA",
  "require_signing": true,
  "require_encryption": false
}
Use either a direct connection or an already mounted resource. Signing and encryption depend on source and server policy.
{
  "id": "nfs-media",
  "public_id": "nfs",
  "type": "nfs",
  "server": "192.168.1.30",
  "export_path": "/exports/media",
  "remote_path": "/library",
  "version": 3,
  "uid": 1000,
  "gid": 1000
}
Use either a direct connection or an export mounted through mount_path. Mounting is recommended for advanced identity or Kerberos setups.
{
  "id": "legacy-media",
  "public_id": "ftp-media",
  "type": "ftp",
  "base_url": "ftp://ftp.example.test/media/",
  "username": "reader",
  "password": "CHANGE_ME",
  "tls_mode": "explicit",
  "verify_tls": true,
  "passive_mode": true,
  "allow_list": true,
  "allow_stream": true,
  "allow_download": false
}
Use FTPS whenever available. Plain FTP requires allow_insecure_ftp=true; the source is read-only and passive mode is the default.
{
  "id": "cdn-catalog",
  "public_id": "cdn",
  "type": "http_manifest",
  "manifest_url": "https://catalog.example.test/media.json",
  "base_url": "https://cdn.example.test/media/",
  "refresh_sec": 300,
  "max_manifest_bytes": 8388608,
  "allowed_origins": ["https://cdn.example.test"],
  "allow_list": true,
  "allow_stream": true,
  "allow_download": false
}
The manifest defines a read-only virtual library. Object URLs must belong to the catalog origin, its base_url or explicit allowed_origins.
{
  "id": "s3-media",
  "public_id": "s3",
  "type": "plugin",
  "plugin_id": "s3-compatible",
  "backend_type": "s3",
  "plugin_config": {
    "endpoint": "https://s3.example.test",
    "bucket": "media",
    "prefix": "library/",
    "region": "us-east-1",
    "access_key": "CHANGE_ME",
    "secret_key": "CHANGE_ME",
    "path_style": true,
    "verify_tls": true
  },
  "allow_list": true,
  "allow_stream": true,
  "allow_download": false
}
The package includes a read-only S3-compatible provider. Additional providers can be installed as extensions while keeping the same permissions, temporary links, limits and endpoints.

Permissions, policies and temporary links

Storage has its own authorization layer. Localhost bypass is disabled by default and knowing a URL does not grant access.

"storage": {
  "localhost_bypass": false,
  "inherit_access_key": true,
  "allow_unauthenticated_remote": false,
  "link_secret": "CHANGE_ME_WITH_A_LONG_RANDOM_SECRET",
  "access_tokens": [
    {
      "token": "CHANGE_ME_READER_TOKEN",
      "permissions": ["storage:list", "storage:read", "storage:prepare"],
      "sources": ["local-media"]
    }
  ]
}
PermissionAction
storage:listView authorized sources and list directories.
storage:readUse HEAD and stream with GET.
storage:downloadUse ?download=1 when the source allows it.
storage:prepareStart, inspect and cancel asynchronous preparation on sources that allow it.
storage:adminView every source and create temporary HMAC links.
Per-source policy
  • public_id hides the internal ID in public URLs.
  • allow_list, allow_stream and allow_download separate capabilities.
  • admin_only reserves a source for administrators.
  • max_concurrent_reads limits one source.
  • Restricted tokens list internal IDs, not public_id.
Signed links

POST /api/storage/sign creates a URL bound to source, path, action and expiration. Changing any of these values invalidates the signature and never bypasses source policy.

Do not place tokens, usernames or passwords in URLs. Use read-only remote accounts and separate example secrets.

API and playback

Every source uses the same public paths and HTTP behaviour.

Method and pathPurpose
GET /api/storageLists sources visible to the principal.
POST /api/storage/<source>/prepareStarts asynchronous preparation; requires storage:prepare.
GET /api/storage/<source>/operations/<id>Returns state, progress and the resulting path.
DELETE /api/storage/<source>/operations/<id>Cancels an operation when supported by the provider.
GET /api/storage/<source>/list?path=...Lists one relative directory.
POST /api/storage/signCreates an exact-path temporary URL.
GET /api/storage/pluginsShows external provider status; requires an explicit Bearer credential with storage:admin.
GET /api/storage/plugin-providersInventories installed providers, versions, capabilities, duplicate IDs and integrity; requires storage:admin.
POST /api/storage/plugins/<source>/restartRestarts one external source; requires an explicit Bearer credential with storage:admin.
GET /storage/<source>/<path>Streams a complete object or one range.
HEAD /storage/<source>/<path>Returns metadata without a body.
GET ...?download=1Forces attachment download when authorized.
One bytes range is supported, including open-ended and suffix ranges, plus If-Range, If-None-Match and If-Modified-Since. Multiple ranges return 416.

curl examples

curl -H "Authorization: Bearer CHANGE_ME_READER_TOKEN" http://127.0.0.1:8180/api/storage

curl -H "Authorization: Bearer CHANGE_ME_READER_TOKEN" "http://127.0.0.1:8180/api/storage/media/list?path=films"

curl -I -H "Authorization: Bearer CHANGE_ME_READER_TOKEN" http://127.0.0.1:8180/storage/media/films/movie.mp4

curl -H "Authorization: Bearer CHANGE_ME_READER_TOKEN" -H "Range: bytes=1048576-2097151" http://127.0.0.1:8180/storage/media/films/movie.mp4

curl -X POST -H "Authorization: Bearer CHANGE_ME_ADMIN_TOKEN" -H "Content-Type: application/json" --data '{"source":"media","path":"films/movie.mp4","ttl_sec":300}' http://127.0.0.1:8180/api/storage/sign

Operations, metrics and troubleshooting

Limits and response codes

ResponseCommon cause
401 / 403Missing identity, permission or source policy.
404Unknown source or object.
416Invalid range or the source does not allow that operation.
429Per-IP transfer limit.
503Global/source limit or temporarily unavailable source.
504Remote server timeout.
Prometheus metrics

The proxy exposes active transfers, bytes, duration, errors, cancellations, ranges, limit rejections and external-provider restarts/failures.

ptp_storage_active_transfers
ptp_storage_bytes_total
ptp_storage_transfer_duration_seconds_count
ptp_storage_transfer_duration_seconds_sum
ptp_storage_errors_total{code="..."}
ptp_storage_cancelled_total
ptp_storage_range_requests_total
ptp_storage_limit_rejections_total{reason="..."}
ptp_storage_plugin_restarts_total{plugin="...",reason="..."}
ptp_storage_plugin_failures_total{plugin="...",code="..."}
Pre-publication checklist
  • Run python3 tests/run_all.py and CTest.
  • Test listing, full playback, seek and disconnect against real infrastructure.
  • Confirm logs contain no credentials or tokens.
  • Use public_id, least privilege and allow_download=false by default.
  • Verify certificates, remote server identity, share/export permissions and HTTP catalog allowed origins.
Asynchronous preparation

Protocol v2 extensions can prepare content that is not immediately available. Clients start the job, poll progress and stream the ordinary result path when it is ready.

curl -X POST -H "Authorization: Bearer CHANGE_ME_PREPARE_TOKEN" -H "Content-Type: application/json" -H "Idempotency-Key: example-1" --data '{"torrent_file":"sample.torrent"}' http://127.0.0.1:8180/api/storage/torrent/prepare

curl -H "Authorization: Bearer CHANGE_ME_PREPARE_TOKEN" http://127.0.0.1:8180/api/storage/torrent/operations/OPERATION_ID

curl -X DELETE -H "Authorization: Bearer CHANGE_ME_PREPARE_TOKEN" http://127.0.0.1:8180/api/storage/torrent/operations/OPERATION_ID
Preparation requires storage:prepare and allow_prepare: true. A read-only token cannot start downloads or remote jobs.

Asynchronous operation states

StateMeaningClient action
queuedAccepted and waiting for resources.Poll again after the suggested interval.
preparingProvider is working and may report progress.Display progress and keep polling.
readyResult now has a playable path.Use the normal /storage URL.
failedOperation ended with a stable error.Show the error or retry with a new key.
cancelledUser or administrator stopped the job.Do not attempt to play the result.

Native, mounted and plugin sources

ClassWho maintains the connectionUse it when
Nativeptp-proxyCommon protocols with first-party support.
MountedOperating systemDrives, FUSE, shares or exports already mounted.
v1 pluginExternal providerContent is immediately available.
v2 pluginExternal provider with operationsContent must be downloaded, restored or generated.
Optional BitTorrent provider

Prepares authorized BitTorrent content and publishes the completed file through the common API. Terminal operations survive restarts, with configurable capacity, free-space reserve, retention and partial cleanup.

ptp-proxy does not supply catalogs or content. Set storage limits before enabling this source and use only authorized material.
Bundled external addons

Four optional providers connect already installed services without expanding the core or changing the public API.

AddonDependencyUseSeek
eD2k / aMuleaMule or aMuled and amulecmdPrepares eD2k links and publishes completed files only.After completion
rclonerclone and a configured remoteExposes cloud or file remotes as read-only storage.Remote-dependent
IPFS / KuboKubo nodeLists CID/IPNS roots and can prepare an authorized root.Yes
Usenet / SABnzbdSABnzbdPrepares authorized NZBs and publishes only the final result.After completion
Control APIs are restricted to loopback by default. Asynchronous jobs require storage:prepare and allow_prepare; install only trusted addons and dependencies.
External provider status

Administrators can inspect active instances, inventory installed providers and restart one source without exposing configuration, credentials or internal paths.

curl -H "Authorization: Bearer CHANGE_ME_ADMIN_TOKEN" http://127.0.0.1:8180/api/storage/plugins

curl -H "Authorization: Bearer CHANGE_ME_ADMIN_TOKEN" http://127.0.0.1:8180/api/storage/plugin-providers

curl -X POST -H "Authorization: Bearer CHANGE_ME_ADMIN_TOKEN" -H "Content-Type: application/json" --data '{}' http://127.0.0.1:8180/api/storage/plugins/s3/restart
These operations require an explicit administrative Bearer credential. The inventory also reports whether package integrity is declared and verified.
External providers

External providers can now be installed to connect additional services without changing the public API. ptp-proxy continues to enforce authentication, permissions, limits, signed links and metrics.

  • The package includes S3, BitTorrent, aMule/eD2k, rclone, IPFS/Kubo and SABnzbd providers.
  • The Python SDK, local example provider and validator support independent extension development and verification.
  • Each provider declares listing, metadata, ranges, seek and asynchronous preparation where applicable.
  • Extensions use the same endpoints, permissions, links and limits as native sources.
  • Install only trusted providers and restrict their credentials and control APIs.

Quick troubleshooting

SymptomCheck
Source is not listedToken, storage:list, source IDs, admin_only and allow_list.
Seek returns 416Range support, known size and remote server capabilities.
WebDAV does not listAuthentication, certificate, root URL, redirects and response limit.
SFTP does not startHost verification, credentials, private key and SSH connectivity.
SMB does not connectCredentials, domain, signing/encryption policy or mount_path.
NFS does not connectExport permissions, version, identity or mount_path.
FTP/FTPS does not connectTLS mode, certificate, passive mode, user and read permissions.
HTTP catalog is rejectedSchema/version, duplicate paths, sizes, URLs and allowed_origins.
External provider does not startCheck /api/storage/plugin-providers and /api/storage/plugins; review duplicate ID, integrity, Python 3, permissions, endpoint and credentials.
BitTorrent preparation is rejectedTotal capacity, free-space reserve, maximum active operations and the size declared by the torrent.
Client disconnectsWrite timeout, remote timeout, network and error metrics.
Included documentation
The package includes a general guide, references for every native source, an external-provider guide and SDK/conformance documentation.

How to test the proxy

1. Verify the server starts

tail -f ptp-proxy.log
curl http://localhost:8180/health

2. Test a channel with ffplay

curl http://localhost:8180/api/channels
ffplay http://localhost:8180/channels/my_channel/playlist.m3u8

3. Test VOD tunnel with a static token (no validation server)

Useful for testing the proxy without a validation server set up. Add to config.json:

"core": {
  "url": "", "secret": "",
  "static_vod_tokens": { "test": "https://your-cdn.com/movie.mkv" }
}
ffplay http://localhost:8180/stream?t=test

4. Test with VLC

  1. Open VLC → Media → Open Network Stream
  2. Enter: http://127.0.0.1:8180/channels/my_channel/playlist.m3u8
  3. Click Play

5. View metrics

curl http://localhost:8180/metrics

6. Test the file server

Use a token with storage:list and storage:read. Test listing, headers and a small range before opening a complete video.

curl -H "Authorization: Bearer CHANGE_ME_LIST_TOKEN" http://localhost:8180/api/storage
curl -H "Authorization: Bearer CHANGE_ME_LIST_TOKEN" "http://localhost:8180/api/storage/media/list?path="
curl -I -H "Authorization: Bearer CHANGE_ME_READ_TOKEN" http://localhost:8180/storage/media/video.mp4
curl -H "Authorization: Bearer CHANGE_ME_READ_TOKEN" -H "Range: bytes=0-1048575" http://localhost:8180/storage/media/video.mp4

7. Check transcoding profiles

curl -H "Authorization: Bearer CHANGE_ME" http://localhost:8180/api/transcode/profiles

8. Check a managed node

In PTP, verify that the node is connected, publishes sources and profiles and can complete a listing job. In the proxy logs, verify that heartbeat messages expose no secrets.

9. Run the automated suite

The suite creates local simulated services and verifies storage, security, plugins, TLS, CONNECT, profiles and asynchronous operations without relying on real providers.

cmake -S . -B build-tests -DPTP_BUILD_STORAGE_TESTS=ON
cmake --build build-tests
ctest --test-dir build-tests --output-on-failure
python3 tests/run_all.py
Recommended real-world checks before publishing
  • Play a large file with VLC or ffplay and perform several seeks.
  • Disconnect a client during transfer and confirm the active counter returns to zero.
  • Test every remote protocol against the actual server you will use.
  • Verify one CPU profile and, where relevant, one GPU profile.
  • Restart a plugin and a v2 operation without losing the final catalogue result.
  • Validate direct and relayed delivery for a remote PTP node.
  • Review logs and metrics for leaked tokens or credentials.

Common troubleshooting

SymptomLikely causeSolution
503 when opening the channelFFmpeg still startingNormal for the first 5–30 s. Wait or increase startup_grace_ms
Permanent 503FFmpeg fails to startCheck the log — look for "ERROR" lines from FFmpeg
NVENC failsOld driver or unsupported GPUUpdate driver to ≥ 570.0 or switch to libx264
Stream cuts / artifactsUnstable source or incorrect GOPAdd -force_key_frames, try libx264
/health not respondingProxy didn't start or port blockedCheck log, check firewall, verify port is correct
curl: (7) Failed to connectProxy is not runningStart the proxy, verify listen and port
Source does not appearMissing permission, policy or configurationCheck storage:list, allow_list, public_id and startup logs
File opens but cannot seekOrigin lacks ranges or size metadataTest HEAD, inspect range_supported and the remote server
Operation remains preparingExternal provider stopped or out of resourcesCheck plugin status, quota, free space and external dependency
PTP node appears offlineURL, TLS or node credential issueCheck managed_node, clock, CA and outbound connectivity
Profile unavailableEncoder or driver missingQuery /api/transcode/profiles and test the profile on that node

Reading logs

The proxy produces structured logs and redacts tokens, signatures, nonces, URLs and sensitive headers. Stream messages only show the upstream origin.

{"lvl":"INFO", "msg":"[stream] type=live mode=hls_proxy upstream=http://cdn:80"}
{"lvl":"WARN", "msg":"proxy_claim HTTP 403: Forbidden IP"}
{"lvl":"ERROR","msg":"ffmpeg exited with code 1"}

Important log fields

FieldDescription
[stream] mode=hls_proxyRequest to /stream resolved as direct HLS proxy
[stream] mode=ffmpeg_restreamRestream channel launched or reused
[stream] mode=vod_tunnelVOD tunnel activated with native seek
[stream] mode=static_bypassStatic token, no validation server call
proxy_claim HTTP 403The validation server rejected the token (expired, not found, IP not authorized)
[connect] tunnel openCONNECT tunnel opened for an HTTP client
ffmpeg exitedFFmpeg closed — may be normal (stream ended) or an error
[storage]Listing, metadata or transfer from a file source.
[plugin]Startup, restart, failure or operation of an external provider.
[managed-node]Enrollment, heartbeat, jobs or reconnection to PTP.
[recording]Scheduling, start, completion or cancellation of a recording.
rate limitAn identity or IP exceeded a configured limit.

What to watch by feature

FeatureLogMetric or check
Proxy channelsMode and redacted originRequests and HTTP status.
RestreamFFmpeg startup and exitActive restreams and uptime.
StorageSource and stable error codeTransfers, bytes, ranges and limits.
PluginsState and restartsRestarts and failures per provider.
PTP nodeHeartbeat, job and reconnectNode presence in PTP.
RecordingsJob stateActive recordings and free space.
Logs should diagnose a flow without exposing tokens, signatures, passwords, credential-bearing URLs, private headers or plugin configuration. Apply the same rule to addon-owned logs.

Useful metrics

  • ptp_storage_active_transfers and transferred bytes.
  • Duration, cancellations, ranges and limit rejections.
  • Plugin restarts and failures.
  • Active restreams and recordings.
  • Uptime and HTTP errors.

Follow logs in real time

tail -f ptp-proxy.log
# Readable format (requires jq):
tail -f ptp-proxy.log | jq -r '.lvl + " | " + .msg'

Security — Protection layers

The proxy combines general HTTP protections with specialised permissions for streams, storage, plugins, CONNECT and managed nodes. Not every route uses the same credential or scope.

Layer 1 — Rate limiting (anti-hammering)

Limits how many requests an IP can make in a time window. If exceeded, returns 429 Too Many Requests.

"rate_limit": {
  "enabled":     true,
  "window_sec":  10,
  "max_requests": 60
}

Layer 2 — Management IPs

Sensitive endpoints (/metrics, /api/*, /proxy/raw) are only accessible from the listed IPs.

"management_ips": ["127.0.0.1", "::1"]

Layer 3 — Access key

When access_key is configured, protected endpoints require Authorization: Bearer. The ?key= query is accepted only when allow_access_key_query is explicitly enabled.

# HTTP header (curl, backends):
curl -H "Authorization: Bearer MY_KEY" http://proxy:8180/channels/my_channel/playlist.m3u8
Legacy compatibility: allow_access_key_query and allow_legacy_url_query are disabled by default. Enable them only while migrating old clients.
With localhost_bypass: true, localhost may skip the key. When binding outside loopback, startup requires an access_key unless allow_unauthenticated_remote is explicitly enabled.

Recommended production configuration

"security": {
  "block_private_ips": true,
  "management_ips":   ["127.0.0.1", "::1"],
  "access_key":       "long-random-key-here",
  "localhost_bypass": true,
  "allow_unauthenticated_remote": false,
  "allow_access_key_query": false,
  "allow_legacy_url_query": false,
  "verify_upstream_tls": false,
  "rate_limit": { "enabled": true, "window_sec": 10, "max_requests": 300 }
}

Surfaces and credentials

SurfaceTypical protectionNote
Channels and playlistsAccess key or claimDo not expose provider URLs.
Dynamic VODHMAC claim tokenCore selects URL, type and profile.
Storagestorage:* scopesPolicy is also evaluated per source.
Temporary linksHMAC signature and expiryBound to source, path and action.
Plugin administrationExplicit storage:adminLocal bypass is not sufficient.
Asynchronous preparationstorage:prepareAlso requires allow_prepare.
PTP nodeNode-specific credentialIssued after one-time enrollment.
CONNECTDedicated secret or claimKeep destinations and origins restricted.
Source isolation

Every source decides whether listing, streaming, downloading or preparation is allowed. Tokens may be restricted to individual sources and public IDs avoid leaking internal identifiers.

Managed-node security

The node connects to PTP, stores its own credential and only executes known jobs. It offers no shell, never returns source credentials and can be revoked without affecting other nodes.

External providers

Install trusted providers only. Verify manifests and integrity, use read-only credentials, limit operations and review the external services they control.

Additional protections

  • SSRF without a second DNS lookup: connections use the exact endpoints that were validated.
  • Every redirect is revalidated against the SSRF policy and allow_hosts.
  • Playlists use opaque contexts; upstream URLs and headers remain in server memory.
  • Secrets and sensitive URLs are redacted from logs; stream messages show only the upstream origin.
  • /health nonces cannot be reused and the cache is bounded to 65,536 entries.
  • Outbound TLS verification is optional and disabled by default; enable it with verify_upstream_tls.

Remote-exposure checklist

  • Use TLS or a secure reverse proxy.
  • Configure a long key and disable query-string compatibility.
  • Restrict management IPs.
  • Keep origin TLS verification enabled where possible.
  • Create storage tokens by permission and source.
  • Set global, per-IP and per-source limits.
  • Do not publish addon control endpoints.
  • Test node revocation and temporary-link expiry.
  • Review logs, metrics and plugin integrity regularly.

Claim system — Secure token authentication

The claim system is how the proxy validates that a streaming token comes from your web service (or any system for which you know the secret). It is based on HMAC-SHA256, the same principle used by JWTs or AWS APIs.

Why is it needed?

Without an authentication system, anyone who knows the proxy URL could:

  • Guess or reuse tokens from other users
  • Force the proxy to access URLs you didn't authorize (SSRF)
  • Use the proxy as an open relay for their own requests

The claim system solves this: the proxy never trusts the client's token directly. It always verifies with your validation server that the token is valid, and authenticates the verification request itself with an HMAC signature.

The full flow for an IPTV client

1. IPTV client requests to play a channel
↓
2. Your web service generates a short token (e.g.: UUID, 30s TTL)
returns to client: http://proxy:8180/stream?t=TOKEN
↓
3. Client calls the proxy: GET /stream?t=TOKEN
↓
4. Proxy builds a signed claim and POSTs to your service:
POST https://your-service.com/api/claim?proxy_claim=1
Body JSON: { token, ts, nonce, sig }
↓
5. Your server verifies the HMAC signature using the shared secret
if OK → returns { ok: true, url: "http://panel/live/USER/PASS/123.m3u8", type: "live" }
↓
6. Proxy serves content to client without exposing the real URL

The client never sees the real credentials. It only sees http://proxy:8180/stream?t=TOKEN.

Why it is secure

Possible attackProtection
Intercept and replay the claimThe claim includes a timestamp (ts) with a ±5-minute window. A captured claim expires quickly.
Brute-force the secretThe secret never travels in plaintext — only the HMAC result does. Without the secret, reversing HMAC-SHA256 is computationally infeasible.
Reuse an expired tokenYour server controls the token TTL in its database. It can invalidate it after first use or after X seconds.
Forge a claimWithout the secret key (core.secret), it is impossible to compute a valid HMAC. All requests without a valid signature receive 403.

HMAC claim calculation — Step by step

This section explains exactly how the proxy builds the claim it sends to your validation server. If you want to implement your own validation server (in any language), this is all you need.

Claim ingredients

FieldTypeDescription
tokenstringThe token the client sent in ?t=TOKEN
tsint (Unix)Current timestamp in seconds since epoch
noncehex string16 cryptographically random bytes, hex-encoded. Example: a3f9c2b1d8e04f72
sighex stringHMAC-SHA256 of the message, in lowercase hex

Exact formula

Message construction and signing
// 1. Build the message by concatenating with "|"
message = token + "|" + ts + "|" + nonce

// Concrete example:
//   token = "e3df09b42ad23f8174eaf28bbe9ad96b"
//   ts    = 1740698753
//   nonce = "a3f9c2b1d8e04f72"
message = "e3df09b42ad23f8174eaf28bbe9ad96b|1740698753|a3f9c2b1d8e04f72"

// 2. Sign with HMAC-SHA256 using the shared secret:
sig = HMAC-SHA256(secret, message)  →  result in lowercase hex

JSON the proxy sends to your server

POST https://your-php.com/api.php?proxy_claim=1
Content-Type: application/json

{
  "token": "e3df09b42ad23f8174eaf28bbe9ad96b",
  "ts":    1740698753,
  "nonce": "a3f9c2b1d8e04f72",
  "sig":   "2b9f3e7d1c45a8f2..."   // 64 hex chars
}

Response your server must return

// Valid token:
{ "ok": true, "url": "http://panel/live/USER/PASS/123.m3u8", "type": "live" }
// Invalid token:
{ "ok": false, "error": "Token not found" }
IMPORTANT: always HTTP 200, even for invalid tokens. A response with HTTP ≠ 200 is treated as a server error.

Stream types supported in the response

typeProxy behavior
liveDownloads and rewrites the HLS playlist from the provider. No FFmpeg. The client never sees the real URL.
restreamDynamically launches FFmpeg with -c copy. Generates its own HLS. Multiple clients share one FFmpeg process.
vodRange-aware tunnel for MP4/MKV. Native seek without buffering.
seriesSame as vod.

Implement your own claims server

If you want to build your own integration, here are reference implementations in several languages.

<?php
// ── Endpoint: api.php?proxy_claim=1 ───────────────────────────────────────────
if (isset($_GET['proxy_claim'])) {
    $body  = json_decode(file_get_contents('php://input'), true);
    $token = $body['token'] ?? null;
    $ts    = $body['ts']    ?? null;
    $nonce = $body['nonce'] ?? null;
    $sig   = $body['sig']   ?? null;
    if (!$token || !$ts || !$nonce || !$sig) {
        http_response_code(400);
        echo json_encode(['ok' => false, 'error' => 'Missing fields']);
        exit;
    }
    // Verify HMAC — the secret NEVER travels in the request
    $secret   = 'YOUR_SHARED_SECRET_HERE';
    $msg      = $token . '|' . $ts . '|' . $nonce;
    $expected = hash_hmac('sha256', $msg, $secret);
    // hash_equals hash_equals prevents timing attacks
    if (!hash_equals($expected, $sig)) {
        echo json_encode(['ok' => false, 'error' => 'Invalid signature']);
        exit;
    }
    // Anti-replay: reject old timestamps (±5 min)
    if (abs(time() - (int)$ts) > 300) {
        echo json_encode(['ok' => false, 'error' => 'Timestamp expired']);
        exit;
    }
    $info = buscar_token_en_db($token);  // your function here
    if (!$info) {
        echo json_encode(['ok' => false, 'error' => 'Token not found']);
        exit;
    }
    echo json_encode(['ok' => true, 'type' => $info['type'], 'url' => $info['url']]);
}
import hmac, hashlib, time, json
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET = 'YOUR_SHARED_SECRET_HERE'

@app.route('/api', methods=['POST'])
def proxy_claim():
    if request.args.get('proxy_claim') != '1':
        return jsonify({'ok': False}), 404
    body  = request.get_json()
    token, ts, nonce, sig = body.get('token'), body.get('ts'), body.get('nonce'), body.get('sig')
    if not all([token, ts, nonce, sig]):
        return jsonify({'ok': False, 'error': 'Missing fields'})
    msg      = f"{token}|{ts}|{nonce}"
    expected = hmac.new(SECRET.encode(), msg.encode(), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, sig):
        return jsonify({'ok': False, 'error': 'Invalid signature'})
    if abs(time.time() - int(ts)) > 300:
        return jsonify({'ok': False, 'error': 'Timestamp expired'})
    info = buscar_token(token)
    if not info: return jsonify({'ok': False, 'error': 'Token not found'})
    return jsonify({'ok': True, 'type': info['type'], 'url': info['url']})
const crypto  = require('crypto');
const express = require('express');
const app     = express();
app.use(express.json());
const SECRET = 'YOUR_SHARED_SECRET_HERE';

app.post('/api', (req, res) => {
    if (req.query.proxy_claim !== '1') return res.status(404).end();
    const { token, ts, nonce, sig } = req.body;
    if (!token || !ts || !nonce || !sig) return res.json({ ok: false, error: 'Missing fields' });
    const msg      = `${token}|${ts}|${nonce}`;
    const expected = crypto.createHmac('sha256', SECRET).update(msg).digest('hex');
    const a = Buffer.from(sig, 'utf8'), b = Buffer.from(expected, 'utf8');
    if (a.length !== b.length || !crypto.timingSafeEqual(a, b))
        return res.json({ ok: false, error: 'Invalid signature' });
    if (Math.abs(Date.now() / 1000 - +ts) > 300)
        return res.json({ ok: false, error: 'Timestamp expired' });
    const info = findToken(token);
    if (!info) return res.json({ ok: false, error: 'Token not found' });
    res.json({ ok: true, type: info.type, url: info.url });
});
package main
import (
    "crypto/hmac"; "crypto/sha256"; "crypto/subtle"
    "encoding/hex"; "encoding/json"; "fmt"; "math"; "net/http"; "time"
)
const secret = "YOUR_SHARED_SECRET_HERE"

type ClaimBody struct {
    Token string `json:"token"`
    Ts    int64  `json:"ts"`
    Nonce string `json:"nonce"`
    Sig   string `json:"sig"`
}
func proxyClaim(w http.ResponseWriter, r *http.Request) {
    var b ClaimBody
    json.NewDecoder(r.Body).Decode(&b)
    msg := fmt.Sprintf("%s|%d|%s", b.Token, b.Ts, b.Nonce)
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(msg))
    expected := hex.EncodeToString(mac.Sum(nil))
    if subtle.ConstantTimeCompare([]byte(expected), []byte(b.Sig)) != 1 {
        json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "error": "Invalid signature"})
        return
    }
    if math.Abs(float64(time.Now().Unix()-b.Ts)) > 300 {
        json.NewEncoder(w).Encode(map[string]interface{}{"ok": false, "error": "Timestamp expired"})
        return
    }
    json.NewEncoder(w).Encode(map[string]interface{}{
        "ok": true, "type": "live", "url": "http://panel/live/USER/PASS/123.m3u8",
    })
}
Important in all languages: always use a constant-time comparison function (hash_equals in PHP, hmac.compare_digest in Python, timingSafeEqual in Node.js, subtle.ConstantTimeCompare in Go). Never compare strings with == for cryptographic data — an attacker can measure comparison timing.

CONNECT proxy — Tunnel outgoing traffic through ptp-proxy

In addition to the claims system for IPTV clients, the proxy includes a handler for HTTP CONNECT requests. This allows any HTTP client (curl, your web app, or any service) to tunnel its outgoing connections through ptp-proxy, so all traffic exits through the network interface of the machine where ptp-proxy is running — regardless of OS. A typical use case is routing your service's requests through a different host or network.

HTTP client ──CONNECT──→ ptp-proxy (any host) ────→ IPTV provider
Without CONNECT: client requests exit through its own network interface
With CONNECT: requests exit through ptp-proxy's network interface (its IP is what the provider sees)

Configuration in config.json

"connect_proxy": {
  "enabled":         true,
  "use_claim":       false,
  "secret":          "YOUR_SECRET_HERE",
  "allowed_origins": ["127.0.0.1", "::1"],
  "allowed_hosts":   []
}
FieldDescriptionDefault
enabledActivates the CONNECT handler. false → 405 on any CONNECTfalse
use_claimfalse → static secret; true → signed HMAC claimfalse
secretShared key. With use_claim: false it is the literal header value; with true it is the HMAC key""
allowed_originsAuthorized source IPs. If the client IP is not here → 403["127.0.0.1","::1"]
allowed_hostsAllowed destination hostnames. Empty = no restriction[]

Two authentication modes for CONNECT

Mode A — Static secret (<code>use_claim: false</code>)

The client sends the secret directly in each request. Simpler. Recommended when the client and the proxy are on the same host.

function curl_set_ptp_proxy_static(CurlHandle $ch): void {
    curl_setopt($ch, CURLOPT_PROXY,       'http://127.0.0.1:8180');
    curl_setopt($ch, CURLOPT_PROXYTYPE,   CURLPROTO_HTTP);
    curl_setopt($ch, CURLOPT_PROXYHEADER, ['X-Proxy-Secret: YOUR_SECRET_HERE']);
}

Mode B — HMAC claim (<code>use_claim: true</code>)

The client signs each CONNECT request with HMAC. The secret never travels in plaintext. Required if the proxy is exposed to the internet.

function curl_set_ptp_proxy_claim(CurlHandle $ch, string $url): void {
    $parsed = parse_url($url);
    $host   = $parsed['host'];
    $port   = $parsed['port'] ?? ($parsed['scheme'] === 'https' ? 443 : 80);
    $ts    = time();
    $nonce = bin2hex(random_bytes(8));
    $msg   = "connect|{$host}:{$port}|{$ts}|{$nonce}";
    $sig   = hash_hmac('sha256', $msg, 'YOUR_SECRET_HERE');
    curl_setopt($ch, CURLOPT_PROXY,       'http://127.0.0.1:8180');
    curl_setopt($ch, CURLOPT_PROXYTYPE,   CURLPROTO_HTTP);
    curl_setopt($ch, CURLOPT_PROXYHEADER, [
        "X-Proxy-Ts: {$ts}",
        "X-Proxy-Nonce: {$nonce}",
        "X-Proxy-Sig: {$sig}",
    ]);
}

HMAC claim formula for CONNECT

HMAC claim formula for CONNECT
message = "connect" + "|" + host + ":" + port + "|" + ts + "|" + nonce
sig     = HMAC-SHA256(secret, message)  →  lowercase hex
Example:  message = "connect|api.provider.com:443|1740698753|a3f9c2b1d8e04f72"
  sig     = hash_hmac('sha256', message, secret)

Safely exposing to the internet

If your service is on a remote server, you need the proxy to accept connections from the internet. With use_claim: true this is safe:

"connect_proxy": {
  "enabled":         true,
  "use_claim":       true,
  "secret":          "random-key-minimum-32-chars",
  "allowed_origins": [],
  "allowed_hosts":   ["api.provider1.com", "api.provider2.com"]
}
Secure
With use_claim: true
  • Secret never travels in plaintext
  • Replay blocked (±60 s window)
  • Destinations restricted by allowed_hosts
  • No secret = no access
Dangerous
Forbidden combinations
  • allowed_origins: [] + use_claim: false + secret: "" → public open proxy
  • allowed_origins: [] + use_claim: false → secret exposed in transit

Generate a secure secret

# Linux / WSL2:
openssl rand -hex 32

API Endpoint Reference

Public reference for channels, VOD, recordings, profiles, storage and administration. Content routes, management routes and storage scopes follow different policies.

EndpointMethodAccessDescription
/  or  /health GET Public Process liveness probe. It may use health protection when exposed beyond loopback.
/playlist.m3u GET Public M3U playlist containing enabled channels and their ptp-proxy HLS URLs.
/channels/<id>/playlist.m3u8 GET Public HLS playlist for a static proxy or restream channel.
/stream?t=<token>[&dl=1] GET Public Resolves a dynamic live, restream, VOD or series claim and returns authorised content.
/download?t=<token> GET Public Download alias for an authorised stream token.
/api/transcode/profiles GET Mgmt only Publishes installed profile IDs, labels, codec and variants without execution commands.
/api/channels GET Mgmt only Lists static channels and selected profiles.
/api/recordings GET / POST Mgmt only Lists or schedules recordings from authorised channels and URLs.
/api/recordings/<id> GET / DELETE Mgmt only Inspects or cancels one recording.
/api/storage GET / HEAD Storage scope Lists sources visible to an identity with storage:list.
/api/storage/<source>/list?path=<relative> GET / HEAD Storage scope Lists folders and files with size, timestamp, MIME, ETag and range support.
/storage/<source>/<path>[?download=1] GET / HEAD Storage scope Delivers an object with ranges, seek, HTTP conditionals and stream/download policy.
/api/storage/sign POST Storage admin Creates a temporary link bound to source, path, action and expiry.
/api/storage/<source>/prepare POST Storage scope Starts an idempotent v2 operation on a preparation-enabled source.
/api/storage/<source>/operations/<id> GET / DELETE Storage scope Reads progress or cancels an asynchronous operation.
/api/storage/plugins GET / HEAD Storage admin Non-sensitive status of configured plugin instances.
/api/storage/plugin-providers GET / HEAD Storage admin Inventory of installed providers, versions, capabilities and integrity.
/api/storage/plugins/<source>/restart POST Storage admin Administratively restarts a plugin source.
/metrics GET Mgmt only Prometheus metrics for server, restream, recording, storage and plugins.
/proxy/raw?url=<encoded> GET Mgmt only Diagnostic proxy for an allowed URL. Do not publish without management controls.
CONNECT <host>:<port> CONNECT Public HTTP CONNECT tunnel subject to its authentication and destination policy.
PTP managed-node channel Outbound HTTPS Outbound channel Agent enrolls, publishes heartbeat and receives jobs from PTP. It does not add an inbound managed API to the node.
Separate content, management and storage

Management routes use general management IPs and key. Storage routes use storage:list, storage:read, storage:download, storage:prepare or storage:admin. The node channel uses its own credential and outbound connection.

Managed channel
PTP integration adds no public inbound management endpoint to ptp-proxy. The agent starts enrollment, heartbeat and long polling toward the PTP node API.

curl quick reference

# List channels
curl -H "Authorization: Bearer CHANGE_ME" http://127.0.0.1:8180/api/channels
# List storage sources
curl -H "Authorization: Bearer CHANGE_ME_LIST_TOKEN" http://127.0.0.1:8180/api/storage
# Read a file range
curl -H "Authorization: Bearer CHANGE_ME_READ_TOKEN" -H "Range: bytes=0-1048575" http://127.0.0.1:8180/storage/media/films/movie.mkv
# Inspect profiles
curl -H "Authorization: Bearer CHANGE_ME" http://127.0.0.1:8180/api/transcode/profiles
# Inspect plugins
curl -H "Authorization: Bearer CHANGE_ME_ADMIN_TOKEN" http://127.0.0.1:8180/api/storage/plugins
# Start preparation
curl -X POST -H "Authorization: Bearer CHANGE_ME_PREPARE_TOKEN" -H "Content-Type: application/json" --data '{"torrent_file":"sample.torrent"}' http://127.0.0.1:8180/api/storage/torrent/prepare

Downloads

Version: 2026-07-16

⚡ Quick start

  1. Download the package for your platform and extract every directory.
  2. Copy config.example.json to config.json.
  3. Choose IPTV channels, storage, plugins, managed mode or any combination.
  4. Install FFmpeg only when you will restream, transcode or record.
  5. Install Python 3 only when using bundled or third-party Python providers.
  6. Linux: chmod +x ptp-proxy && ./ptp-proxy --config config.json.
  7. Windows: ptp-proxy.exe --config config.json.
  8. Check /health, /api/transcode/profiles and, when sources are configured, /api/storage.

Package contents

  • ptp-proxy binary for the selected platform.
  • Example configuration and bilingual documentation.
  • PTP-compatible transcoding profiles.
  • SDK and bundled external providers.
  • Test and release-verification scripts where applicable.
2026-07-16 4 files
Platform File Size SHA-256
🪟 Windows (x64) ptp-proxy-20260716-windows-amd64.zip 4.9 MB c4fa9424… Download
🐧 Linux (x86_64) ptp-proxy-20260716-linux.zip 1.4 MB 15df47cb… Download
🐧 Linux ARM64 (RPi 3/4/5, aarch64) ptp-proxy-20260716-linux-arm64.zip 1.3 MB 5af960a7… Download
🐧 Linux ARMv7 (RPi 1/2/Zero, 32-bit) ptp-proxy-20260716-linux-armv7.zip 1.2 MB 74504de8… Download
2026-03-07 4 files
Platform File Size SHA-256
🪟 Windows (x64) ptp-proxy-20260307-windows.zip 3.3 MB 9944f8e1… Download
🐧 Linux (x86_64) ptp-proxy-20260307-linux.zip 2.7 MB f7f584ce… Download
🐧 Linux ARM64 (RPi 3/4/5, aarch64) ptp-proxy-20260307-linux-arm64.zip 2.4 MB e2d7fcd3… Download
🐧 Linux ARMv7 (RPi 1/2/Zero, 32-bit) ptp-proxy-20260307-linux-armv7.zip 2 MB 73148ea1… Download
2026-02-28 4 files
Platform File Size SHA-256
🪟 Windows (x64) ptp-proxy-20260228-windows.zip 0.7 MB 628a1786… Download
🐧 Linux (x86_64) ptp-proxy-20260228-linux.zip 2.7 MB 13012661… Download
🐧 Linux ARM64 (RPi 3/4/5, aarch64) ptp-proxy-20260228-linux-arm64.zip 2.4 MB 50674565… Download
🐧 Linux ARMv7 (RPi 1/2/Zero, 32-bit) ptp-proxy-20260228-linux-armv7.zip 2 MB 4b511a5c… Download

🐳 Docker

Docker image with all plugins, profiles and documentation included.

docker pull hamboy75/ptp-proxy Docker Hub →