# mf - Media File Finder

<!-- Local badges generated by CI into static/: coverage (anybadge from coverage.xml line-rate), python minimum version (MIN+ from requires-python) -->
![Python Versions](./static/badge_python.svg)
![Tests](https://img.shields.io/github/actions/workflow/status/aplzr/mf/ci.yml?branch=main&label=tests)
![Coverage](./static/badge_coverage.svg)
[![PyPI release](https://img.shields.io/pypi/v/mediafinder)](https://pypi.org/project/mediafinder/)
![Style](./static/badge_style.svg)

A cross-platform command-line tool for finding and playing video files in large collections

![](static/demo_mf.svg)

## Features

- **🔍 Fast file search** - Uses vendored `fd` binary with automatic fallback to Python scanning
- **🎯 Flexible pattern matching** - Glob-based search patterns with automatic wildcard wrapping
- **🚀 Quick access to search results** - Access found files by index number
- **📋 Search results as playlists** - Play through search results in order
- **💾 Caching of library metadata** - Cache library metadata for fast file lookups in network storage
- **📁 Multi-path scanning** - Search across multiple configured directories simultaneously
- **🕒 Latest additions** - Find newest files by modification time
- **🎬 Media player integration** - Launch files directly in VLC or mpv
- **🌐 IMDB lookup** - Automatically open IMDB pages for media files
- **⚙️ Flexible configuration** - TOML-based config with extension filtering and path management
- **🖥️ Cross-platform** - Works on Windows, Linux, and macOS

## Installation
Install with [uv](https://docs.astral.sh/uv/):

```
uv tool install mediafinder
```

## Quick Start

> [!NOTE]
> If `mf` is shadowed by another command on your system (e.g., Metafont), use `mediafinder` instead — it's already installed and works exactly the same way.

1. **Configure search paths** where your media files are located:

```bash
mf config set search_paths "/path/to/movies" "/path/to/tv-shows"
```

2. **Optional: Activate library caching**

If your collection is stored on the network, activate caching for fast file searches:

```bash
mf config set cache_library true
```

3. **Find media files** matching a filename pattern:

```bash
mf find "batman" # Finds files containing "batman"
mf find "*.mp4" # Finds all MP4 files
mf find "2023" # Finds files from 2023
```

4. **Play a file** from search results:

```bash
mf play 1 # Play first result
mf play next # Search results as playlist: play next result
mf play list # Send current search results to video player as a playlist
mf play # Play random file
```

5. **Find latest additions**:
```bash
mf new # Show 20 newest files
mf new 50 # Show 50 newest files
```

## Commands

### Core Commands

- `mf find [pattern]` - Search for media files matching the glob pattern
- `mf new [n]` - Show latest additions (default: 20 files)
- `mf play [index]` - Play a file by index, or random file if no index given
- `mf imdb <index>` - Open IMDB page for a media file
- `mf filepath <index>` - Print full path of a search result
- `mf version` - Print version information or run version check
- `mf cleanup` - Reset `mediafinder` by deleting all configuration and cache files
- `mf stats` - Show library statistics
- `mf last` - Show last search result index with the last played file highlighted
- `mf config` - Configure `mf`
- `mf cache` - Manage library cache (rebuild, show location, clear)
- `mf` or `mf --help` - Print help


### Last search results

- `mf last` / `mf last show`- Show indexed results from the last file search
- `mf last file` - Print search result file location
- `mf last clear` - Clear last search results

Cached indices remain valid until you run another `find` or `new` command that overwrites the last search.

### Cache Management

**Configuration:**

- `mf config set cache_library <true/false>` - Turn library caching on or off
- `mf config set library_cache_interval <interval>` - Set auto-rebuild interval in seconds (default 86400 (1 day), 0 to disable)

**Manual control:**

- `mf cache rebuild` - Trigger a library cache rebuild
- `mf cache file` - Print cache location
- `mf cache clear` - Clear the cache
- `mf stats` - Show library statistics


### Configuration Management
`mf` can be configured directly on the command line:

- `mf config set <key> <values>` - Set configuration values
- `mf config add <key> <values>` - Add values to list settings
- `mf config remove <key> <values>` - Remove values from list settings
- `mf config get <key>` - Get configuration value
- `mf config list` - Show the current configuration
- `mf config edit` - Edit config file in default editor
- `mf config file` - Print config file location
- `mf config settings` - Print a table of all available settings

#### Search Paths
Add multiple paths of a (scattered) collection:

```bash
mf config set search_paths "/movies" "/tv-shows" "/documentaries"
```

#### Media Extensions
Control which file types are considered media files:

```
mf config set media_extensions ".mp4" ".mkv" ".avi" ".mov" ".wmv"
```

#### Extension Matching
Toggle whether to filter results by media extensions:

```bash
mf config set match_extensions true # Only return configured media types
mf config set match_extensions false # Return all files matching pattern
```

#### Automatic wildcard wrapping
Toggle whether search patterns should be wrapped with wildcards automatically:

```bash
# Wraps search patterns with '*' if no wildcards (* ? [ ]) present.
# 'mf find batman' searches for files matching *batman*.
mf config set auto_wildcards true

# Does not wrap search patterns.
# 'mf find batman' searches for files exactly named batman.
mf config set auto_wildcards false
```

#### Parallel search
Toggle whether file searches should be parallelized over search paths:

```bash
# Runs file searches concurrently over all paths defined in search_paths (potentially faster).
mf config set parallel_search true

# Runs file searches sequentially over all paths defined in search_paths. Use this if
# your search paths are on the same mechanical (but not solid state) hard drive to avoid
# disk thrashing.
mf config set parallel_search false
```

#### Other Settings

- `video_player` (str): Video player to use (`vlc`, `mpv`, or `auto`). Default is `auto`, which prefers VLC with automatic fallback to mpv.
- `fullscreen_playback` (bool): If true, `mf play` launches the video player in fullscreen mode.
- `prefer_fd` (bool): Use the bundled `fd` scanner when possible. Automatically ignored for mtime-sorted searches (`mf new`) which always use the Python scanner.
- `display_paths` (bool): Turn filepath display in search results on or off.

#### Editing the Config

`mf config edit` resolves an editor in this order:
1. `$VISUAL` or `$EDITOR`
2. Windows: Notepad++ if present else Notepad
3. POSIX: first available of `nano`, `vim`, `vi`

If no editor is found, it prints the path so you can edit manually.

#### Input Normalization

- Boolean values accept: `true`, `false`, `yes`, `no`, `y`, `n`, `on`, `off`, `1`, `0` (case-insensitive; synonyms normalized to true/false).
- Media extensions are normalized to lowercase with a leading dot (`mkv` → `.mkv`).
- Paths are stored as absolute POSIX-style strings.

## Search Patterns

- Use quotes around patterns with wildcards to prevent shell expansion
- Patterns without wildcards are automatically wrapped: `batman` becomes `*batman*`
- Automatic wildcard wrapping only happens if the pattern contains none of: `* ? [ ]`.
- Examples:
  - `mf find "*.mp4"` - All MP4 files
  - `mf find batman` - Files containing "batman"
  - `mf find "*2023*1080p*"` - 2023 releases in 1080p
  - `mf find "s01e??"` - Season 1 episodes

## Integration Features

- **Video Player Integration**: Automatically launches VLC or mpv media player with configurable preference
- **IMDB Lookup**: Uses filename parsing to find matching IMDB entries
- **Smart Caching**: Search results are cached for quick index-based access
- **Cross-platform paths**: Handles Windows and Unix path conventions
- **Random Playback**: `mf play` (without index) randomly selects a file by scanning all configured paths (not just the last cached search).

## Performance

- Uses bundled `fd` binary for fast file scanning when possible
- Automatic fallback to Python scanning if `fd` unavailable
- Parallel scanning across multiple search paths

### Benchmarking `fd` vs pure python file scanning
- All tests with [`hyperfine`](https://github.com/sharkdp/hyperfine) and warm caches: `hyperfine --warmup 3 --runs 10 "mf find test"`.
- Media collection on two separate, mechanical USB drives in a file server on the local network, served via SMB for Windows and NFS for Linux clients, 16.3 TiB / 3540 files total.
- Tested on the file server itself with local file access as well as on a Linux and a Windows desktop with network file access.
- `mf find` can use both the `fd` scanner as well as the pure python one. First run was with the default setting `prefer_fd = true`. After that I switched to the python scanner via `mf config set prefer_fd false` and tested again.

| Platform | Pure Python (ms) | `fd` Scanner (ms) | Improvement |
|:---------|------------------:|----------------:|------------:|
| **Linux Server (local file access)** | 697.9 ± 17.1 | 443.5 ± 2.6 | **36% faster** |
| **Linux Desktop (NFS)** | 1,618.0 ± 28.0 | 478.2 ± 21.2 | **70% faster** |
| **Windows Desktop (SMB)** | 2,371.0 ± 90.0 | 1,601.0 ± 94.0 | **32% faster** |

- The `fd` scanner provides 32-70% performance improvement for search operations over pure python file scanning.


## Requirements

- Python 3.10+
- VLC or mpv media player (for `play` command)
- Internet connection (for IMDB lookup)


## License

This project is licensed under the MIT License - see the [LICENSE-MIT](LICENSE-MIT) file for details.

### Third-Party Software

This package includes the [`fd`](https://github.com/sharkdp/fd) file finder binary, which is dual-licensed under **MIT OR Apache-2.0**.
Copyright (c) 2017-present the fd developers.

- MIT License: See `src/mf/bin/LICENSE-fd-MIT`
- Apache License 2.0: See `src/mf/bin/LICENSE-fd-APACHE`
