Skip to content

ChromaPrint Integration ​

Overview ​

ChromaPrint (via libchromaprint/acoustid) provides audio fingerprinting capabilities for the Phlix media server. This enables episode grouping and skip-intro/outro detection in Phase F.

Implementation Architecture ​

FFI vs Shelled Mode ​

The implementation supports two backends:

  1. FFI (preferred): Direct calls to libchromaprint.so via PHP's FFI extension. Zero binary dependency, faster execution.
  2. Shelled (fallback): Wraps the fpcalc command-line tool via proc_open(). Used when FFI is disabled (common in shared hosting).

The factory (ChromaPrintFactory) automatically selects FFI when available, falling back to shelled mode.

Key Classes ​

ClassPurpose
ChromaPrintInterfaceInterface defining fingerprint() and isAvailable()
ChromaPrintMain wrapper class for clients
ChromaPrintFfiFFI implementation
ChromaPrintShelledShelled fpcalc implementation
ChromaPrintFactoryFactory that auto-selects FFI/shelled
ChromaPrintNotAvailableExceptionThrown when no backend is available
ChromaPrintFingerprintFailedExceptionThrown when fingerprinting fails
FingerprintRepositoryPersists fingerprints to media_items.metadata_json

Configuration ​

Edit config/chromaprint.php:

php
return [
    'enabled' => true,
    'fpcalc_path' => '/usr/local/bin/fpcalc',
    'use_ffi_first' => true,
    'fingerprint_audio_seconds' => 120,  // 0 = full file
    'skip_if_duration_lt' => 300,        // don't fingerprint items < 5 min
];

Config Keys ​

  • enabled: Enable/disable fingerprinting (future F.2 use)
  • fpcalc_path: Path to fpcalc binary for shelled mode
  • use_ffi_first: Whether to try FFI before shelled (always true, reserved)
  • fingerprint_audio_seconds: Seconds of audio to fingerprint (default: 120 = 2 min)
  • skip_if_duration_lt: Skip fingerprinting for media < N seconds

Fingerprint Storage ​

Fingerprints are stored in media_items.metadata_json under the fingerprint key:

json
{
  "fingerprint": "ABC123DEF456...",
  "existing_key": "existing_value"
}

No schema changes required at this stage.

Requirements ​

  • FFI mode: PHP 7.4+ with ffi.enable=1 in php.ini, libchromaprint.so installed
  • Shelled mode: fpcalc binary installed and in $PATH or at configured fpcalc_path

Usage ​

php
use Phlix\Media\Markers\Fingerprinting\ChromaPrint;
use Phlix\Media\Markers\Fingerprinting\FingerprintRepository;
use Phlix\Media\Library\ItemRepository;

// Generate a fingerprint
$chromaprint = new ChromaPrint('/usr/local/bin/fpcalc');
if ($chromaprint->isAvailable()) {
    $fingerprint = $chromaprint->fingerprint('/path/to/episode.mkv');
}

// Store and retrieve
$repo = new FingerprintRepository($itemRepository);
$repo->storeFingerprint('media-item-id', $fingerprint);
$fp = $repo->getFingerprint('media-item-id');

// Get all fingerprinted episodes for a show
$fingerprintedIds = $repo->getFingerprintedIdsForShow('show-id');

FFI Details ​

The FFI implementation uses:

c
char* chromaprint_generate_fingerprint(const char* path);
void chromaprint_free_fingerprint(char* fingerprint);

Library detection paths checked:

  • /usr/lib/libchromaprint.so
  • /usr/lib64/libchromaprint.so
  • /usr/local/lib/libchromaprint.so
  • /usr/local/lib64/libchromaprint.so

Shelled fpcalc Details ​

The shelled implementation:

  • Uses proc_open() with 60-second timeout
  • Parses FINGERPRINT=<value> from stdout
  • Checks binary existence and executability
  • Validates functional output via fpcalc -help

How Fingerprints Feed Into Intro/Outro Detection ​

In Phase F.2, fingerprints will be used to:

  1. Cluster episodes of the same show by similarity
  2. Group episodes shot with same equipment/studio
  3. Identify recurring intro/outro segments across episodes

The 120-second default captures enough audio for clustering without processing entire files.

Future Phases ​

  • F.2: Background job to fingerprint all episodes, cluster by similarity
  • F.3: Detect intro/outro segments using fingerprint clusters

BSD-3-Clause