Metadata-Version: 2.4
Name: py-media-server
Version: 0.4.1
Summary: A simple media server for streaming video files
Home-page: https://github.com/pandiyarajk/py-media-server
Author: Pandiyaraj Karuppasamy
Author-email: Pandiyaraj Karuppasamy <pandiyarajk@live.com>
License: MIT
Project-URL: Homepage, https://github.com/pandiyarajk/py-media-server
Project-URL: Bug Reports, https://github.com/pandiyarajk/py-media-server/issues
Project-URL: Source, https://github.com/pandiyarajk/py-media-server
Keywords: media,server,streaming,video,http
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: End Users/Desktop
Classifier: Topic :: Multimedia :: Video
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: author
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-python

# py-media-server

> **Disclaimer.** This software is provided **AS IS**, without warranty of any kind, express
> or implied. **Use it entirely at your own risk.** By default it listens on every network
> interface **without a password**: anyone who can reach the computer can browse and stream
> every video in the folder you serve. It also writes thumbnails and a playback cache into a
> `.thumbs` folder there. The author accepts no liability for data loss, data corruption,
> exposure of files, hardware damage, business interruption or any consequential damages
> arising from its use. You are responsible for choosing what you serve and on which network,
> for being authorised to share it, and for keeping tested backups. It is not certified for
> regulated, forensic, safety-critical or high-assurance use. The [LICENSE](LICENSE) is the
> governing text and prevails wherever it and this notice differ. See also
> [DISCLAIMER.md](DISCLAIMER.md).

A simple, lightweight media server for streaming video files over HTTP with a modern web interface.

## Features

- 🎬 Stream video files (MP4, MKV, AVI, MOV, M4V, WebM) from any directory
- 📂 Recursive scanning: subfolders are browsed, filtered and searched
- 🔍 Search, sort (name / newest / largest / longest) and folder filter,
  all preserved in the URL so a view can be bookmarked or shared
- ⏯️ Resume where you left off, with a *Continue watching* row and progress
  bars on tiles
- 💬 Sidecar subtitles (`.srt` / `.vtt`) discovered automatically and served
  as WebVTT, with language labels
- ▶️ MKV and other non-native files play automatically: ffprobe inspects
  every audio track and the server remuxes, converts the audio, or converts
  the video, whichever is cheapest for the browser in front of it
- ▶️ Full custom player: play/pause, ±10s skip, next/previous, seek bar,
  volume, playback speed, fullscreen and keyboard shortcuts
- ⏭️ Auto-advance to the next video when one finishes
- 🖼️ Automatic thumbnail generation using FFmpeg
- 📱 Responsive web interface that works on mobile and desktop
- ⚡ Range request support for efficient video streaming
- 🔒 Threaded TCP server for concurrent connections
- 🎨 Modern, dark-themed UI

## Installation

### From PyPI (when published)

```bash
pip install py-media-server
```

### From source

```bash
git clone https://github.com/pandiyarajk/py-media-server.git
cd py-media-server
pip install -e .
```

## Requirements

- Python 3.11+
- FFmpeg and ffprobe (optional)

FFmpeg is optional but unlocks three features: thumbnail generation, codec
inspection (duration, resolution, audio tracks) and on-the-fly remuxing and
transcoding. Without it the server still browses and streams: the affordances
that would not work are simply not rendered, and files the browser is unlikely
to play are badged "may not play".

The server looks for `ffmpeg`/`ffprobe` on `PATH` first, then in common
install locations (`C:\ffmpeg\bin`, Homebrew, `/usr/local/bin`, the winget
shim directory, etc.), so a normal install is picked up even when it was never
added to `PATH`. If it lives somewhere unusual, point at it directly:

```bash
py-media-server ~/Videos --ffmpeg /opt/custom/ffmpeg/bin
# or the binary itself:
py-media-server ~/Videos --ffmpeg /opt/custom/ffmpeg/bin/ffmpeg
```

`PY_MEDIA_SERVER_FFMPEG` (path to the binary or its directory) works the same
way as an environment variable.

To install FFmpeg:
- **Windows**: Download from [ffmpeg.org](https://ffmpeg.org/download.html) or use `choco install ffmpeg`
- **macOS**: `brew install ffmpeg`
- **Linux**: `sudo apt-get install ffmpeg` or `sudo yum install ffmpeg`

## Usage

### Basic usage

Serve media files from the current directory:

```bash
py-media-server
```

Or specify a directory:

```bash
py-media-server /path/to/your/movies
```

### Custom port

```bash
py-media-server /path/to/your/movies --port 9000
```

### Custom host and port

```bash
py-media-server ~/Videos --host 192.168.1.100 --port 8080
```

### Help

```bash
py-media-server --help
```

## Command-line Options

```
positional arguments:
  directory             Directory containing media files to serve (default: current directory)

optional arguments:
  -h, --help            show this help message and exit
  -p PORT, --port PORT  Port to run the server on (default: 8000)
  --host HOST           Host to bind the server to (default: 0.0.0.0)
  --ffmpeg PATH         Path to ffmpeg, or the directory holding it, for
                        installs not on PATH or in a common location
  -v, --version         show program's version number and exit
```

## How it Works

1. **Start the server**: Point it to a directory containing video files
2. **Open your browser**: Navigate to `http://localhost:8000`
3. **Browse and play**: Click on any video thumbnail to start streaming

The server will:
- Scan the directory **and its subfolders** for supported video formats
  (directories beginning with `.` are skipped)
- Generate thumbnails automatically (stored in the `.thumbs` subdirectory,
  alongside cached probe metadata and watch progress)
- Serve videos with range request support for smooth playback
- Handle concurrent connections efficiently

### Library view

The index page offers a search box, a sort selector and a folder filter. The
active view is written to the URL fragment, so a filtered listing can be
bookmarked or shared. Files you have started appear in a **Continue watching**
row, and every tile shows a progress bar plus badges for resolution, duration,
subtitles and how the file will be played (see below).

### Subtitles

Sidecar files next to the video are picked up automatically:

```
Shows/S01/ep01.mkv
Shows/S01/ep01.srt          -> "Subtitles"
Shows/S01/ep01.en.srt       -> "EN"
Shows/S01/ep01.English.vtt  -> "English"
```

SRT is converted to WebVTT on the fly, so no pre-processing is needed.
Encodings other than UTF-8 (including BOM-prefixed and CP1252 files) are
decoded rather than rejected.

### Compatibility and transcoding

When ffprobe is available the server records the video codec and **every**
audio track of each file (codec, channels, language, default flag), probing
the library in the background and caching the result in `.thumbs`. Each time
a file is opened it picks a playback plan, cheapest first:

| Plan | When | What ffmpeg does |
|---|---|---|
| direct | MP4/MOV/WebM, decodable video, first audio track decodable | nothing; the file is served with range requests |
| remux | decodable video and some decodable audio track, but an MKV/AVI container or the good track is not first | copies one video and one audio stream into fragmented MP4 (no re-encode, almost no CPU) |
| audio | decodable video, no decodable audio track (EAC3/AC3/DTS only) | copies the video, converts the main audio track to stereo AAC |
| full | the browser cannot decode the video (for example HEVC on a browser without HEVC support) | re-encodes the video to H.264 and copies or converts the audio |

Streams are always mapped explicitly. A typical MKV carries a 5.1 EAC3 track
first and a stereo AAC track second; the AAC track is chosen (preferring the
film's own language, the default flag, then stereo) and copied, so the video
neither plays silently nor needs a re-encode.

The player asks the browser what it can decode (`MediaSource.isTypeSupported`,
falling back to `canPlayType`) and tells the server, for example
`?hevc=1`. Chrome and Edge on Windows with hardware HEVC decoding then get
HEVC copied into MP4 tagged `hvc1`; other browsers get it converted. AC3 and
EAC3 support is asked the same way. A browser that reports nothing gets the
conservative plan.

The player starts the chosen plan by itself. If playback fails, or a few
seconds in the browser has decoded no video or no audio, it moves on to the
next plan automatically. A **Direct / Remux / Convert audio / Convert video**
selector beside the subtitles menu switches by hand. Seeking a converted
stream restarts ffmpeg at the new point, and the time display, resume and
sidecar subtitles all stay on the file's own clock.

Library badges say what will happen: nothing for direct play, a small
container chip (such as `MKV`) for a remux, `converts audio`, or
`converts video` (CPU heavy). HEVC files are labelled for the browser viewing
the page. `may not play` appears only when ffmpeg is unavailable and the file
is not browser-native.

Up to six remuxes and two conversions run at once; a request waits up to ten
seconds for a slot before getting a `503`. ffmpeg is stopped as soon as the
viewer seeks, switches mode or closes the tab.

## Player

Clicking a tile opens the player at `/play/<filename>`. The page is served with
the whole library as its playlist, so next/previous move through the same
sorted listing shown on the home page (disabled at the ends).

### Controls

| Button | Action |
|---|---|
| ⏮ | Previous video |
| ⏪ | Back 10 seconds |
| ▶ / ⏸ | Play / pause |
| ⏩ | Forward 10 seconds |
| ⏭ | Next video |
| 🔊 | Mute, with a volume slider |
| 1x | Playback speed (0.5x – 2x) |
| ⛶ | Fullscreen |

A draggable seek bar shows elapsed and total time.

### Keyboard shortcuts

| Key | Action |
|---|---|
| `Space` / `K` | Play / pause |
| `←` / `→` | Seek 10 seconds |
| `↑` / `↓` | Volume up / down |
| `N` | Next video |
| `P` | Previous video |
| `M` | Mute |
| `C` | Cycle subtitle track |
| `F` | Fullscreen |

## Development

### Setup development environment

```bash
git clone https://github.com/pandiyarajk/py-media-server.git
cd py-media-server
pip install -e ".[dev]"
```

### Run tests

```bash
pytest
```

### Run tests with coverage

```bash
pytest --cov=py_media_server --cov-report=html
```

## Project Structure

```
py-media-server/
├── py_media_server/
│   ├── __init__.py
│   ├── server.py
│   └── templates.py
├── tests/
│   ├── __init__.py
│   └── test_server.py
├── setup.py
├── pyproject.toml
├── README.md
├── LICENSE
└── MANIFEST.in
```

## Security Notice

⚠️ **Warning**: This server is intended for local network use only. It does not include authentication or encryption. Do not expose it to the public internet without proper security measures.

## License

MIT License - see LICENSE file for details

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## Changelog

### 0.4.1 (2026-10-19)
- Removed the keyboard and touch help line under the player; the shortcuts
  still work and stay documented here

### 0.4.0 (2026-10-19)
- Fullscreen shows the player controls (seek bar, back/forward, mute) as an
  overlay instead of a bare video, on desktop, phone and TV browsers
- Controls fade out after 3 seconds of playback and return on mouse move, tap
  or key press
- Touch: double-tap the left or right side of the video to skip back or
  forward, with a +/-10s flash; tap shows or hides the controls
- One mute path for the button, the `M` key and the remote's mute key; media
  keys (rewind, fast-forward, play/pause) work, and the D-pad can move between
  buttons
- Larger touch targets, visible focus outline, volume slider hidden on narrow
  screens (use the hardware keys)
- The startup banner lists the LAN address to open from a TV or phone

### 0.3.1 (2026-10-18)
- MKV files with browser-safe codecs play through a stream-copy remux instead
  of being badged "may not play"
- ffprobe records every audio track; a file with EAC3 first and AAC second
  plays the AAC track instead of playing silently
- ffmpeg streams are mapped explicitly (`-map 0:v:N -map 0:a:N`) instead of
  letting ffmpeg pick the 5.1 EAC3 track; cover-art streams are skipped
- Playback plan (direct, remux, audio, full) chosen per file and per browser;
  the player starts it automatically and falls back on failure or silence
- HEVC is copied as `hvc1` for browsers that report HEVC support, converted
  for others; AC3/EAC3 support is detected the same way
- Manual Direct / Remux / Convert selector in the player
- Seeking a converted stream keeps the time display, resume position and
  sidecar subtitles in sync (`/subs/...?offset=N`)
- Honest library badges: container chip, "converts audio", "converts video";
  "may not play" only without ffmpeg
- Metadata cache entries from 0.3.0 still load and are re-probed
  automatically
- Remuxes and conversions use separate slot pools, and a seek waits for a
  slot instead of failing with "busy"

### 0.3.0 (2026-08-15)
- ffmpeg/ffprobe are discovered in common install locations, and via
  `--ffmpeg` / `PY_MEDIA_SERVER_FFMPEG`, when absent from PATH
- Recursive scanning of subfolders
- Search, sort and folder filtering, with the view reflected in the URL
- Resume playback, "Continue watching" row and per-tile progress bars
- Sidecar subtitle discovery with SRT-to-WebVTT conversion
- Codec compatibility warnings via ffprobe
- On-demand transcoding for files the browser cannot play
- Optional features are hidden when ffmpeg is unavailable rather than
  rendered broken
- Page markup and scripts split out into `templates.py`
- Minimum supported Python raised to 3.11

### 0.2.0 (2026-08-15)
- Full custom player: play/pause, ±10s skip, next/previous, seek bar, volume,
  playback speed, fullscreen, keyboard shortcuts, auto-advance
- Unmatched routes now return 404 instead of closing the connection with no
  response
- Range requests are clamped to the file size
- Filenames are escaped when embedded in page scripts

### 0.1.0 (2026-04-19)
- Initial release
- Basic video streaming functionality
- Thumbnail generation
- Web interface
- Command-line interface
