Skip to content

Schedules Direct EPG Integration

Since: 0.12.0

Schedules Direct (https://www.schedulesdirect.org) provides authoritative TV guide data including program listings, series info, artwork, and ratings. This integration allows Phlix to sync EPG data from SD's JSON API.

Overview

The SD EPG integration is implemented as an optional LiveTV subsystem that can sync program guide data after channel discovery. It requires a valid SD account subscription.

Architecture

SdApiClient          — HTTP JSON client, token auth, BASE_URL: https://api.schedulesdirect.tmsglobal.com
SdLineupHandler      — Fetches SD lineups, imports channels via ChannelManager
SdProgramMapper      — Maps SD schedule/program data to GuideManager::upsertProgram() format
SdEpgService        — Orchestrates full sync: fetch schedules, programs, upsert to guide
SdEpgServiceFactory — Builds service from config with token caching to filesystem

Authentication

SD uses token-based authentication (Bearer token in Authorization header). Tokens can be:

  1. Pre-seeded — Token stored at token_cache_path from a previous session
  2. Auto-fetched — Credentials in config used to obtain token via HTTP Basic Auth

Token caching: Tokens are cached with a 23-hour TTL and refreshed automatically.

Obtaining Credentials

  1. Create an account at https://www.schedulesdirect.org
  2. Subscription required (typically ~$25/year)
  3. Add username/password to config/livetv.php under schedules_direct

Configuration

Add to config/livetv.php:

php
'schedules_direct' => [
    'enabled' => true,
    'username' => 'your_sd_username',
    'password' => 'your_sd_password',
    'token_cache_path' => '/var/phlix/sd_token.json',
    'lineup_id' => null,           // null = auto-detect, or set 'USA-OTA-XXXXX'
    'sync_hours_ahead' => 336,      // 14 days (SD limit)
    'timeout_secs' => 30,
],

API Endpoints Used

MethodEndpointPurpose
GET/tokenValidate token
POST/tokenObtain token (HTTP Basic Auth)
GET/lineupsList available lineups
GET/headend/{systemId}/stationGet stations in a lineup
POST/schedules/md5Get schedule MD5 hashes (change detection)
POST/schedulesGet full schedule data
POST/programsGet program metadata

Data Model

SD Schedule Entry

json
{
  "stationID": "station123",
  "programID": "EP0012345678901",
  "airDateTime": "2024-01-15T14:00:00Z",
  "duration": 60,
  "isRepeat": false
}

SD Program Data

json
{
  "programID": "EP0012345678901",
  "title": "Program Title",
  "description": "Program description",
  "entityType": "episode",
  "seasonNumber": 1,
  "episodeNumber": 5,
  "genres": ["Drama", "Series"],
  "contentRating": ["TV-PG"],
  "originalAirDate": "2024-01-15"
}

Program ID Generation

Program IDs are generated deterministically from channel ID + start time:

program_id = md5(channel_id . '_' . start_timestamp)

This ensures the same program slot always gets the same ID, enabling efficient upserts without duplicates.

Channel Mapping

SD stations are mapped to Phlix channels with:

  • name → callSign
  • number → channelNumber / logicalChannelNumber
  • tuner_idsd_ + stationID
  • service_id → stationID
  • icon_url → logo.URL

Usage

Via LiveTvManager

php
// After construction, set SD config
$manager->setSdConfig($config['schedules_direct']);

// Run EPG sync (auto-builds SdEpgService)
$stats = $manager->syncSdEpG(daysAhead: 14);
echo "Imported: {$stats['imported']}, Errors: {$stats['errors']}";

Direct Service Usage

php
use Phlix\LiveTv\Epg\SchedulesDirect\SdEpgServiceFactory;

$service = SdEpgServiceFactory::build(
    $config['schedules_direct'],
    $channelManager,
    $guideManager
);

$stats = $service->syncEpg(['station1', 'station2'], 14);

Lineup Import + Sync

php
$result = $service->importLineupAndSync('USA-OTA-00000', 14);
// $result = ['channels' => [...], 'stats' => ['imported' => N, 'errors' => M]]

Program Mapping

SD program data is mapped to GuideManager categories:

SD GenreGuideManager Category
MovieCATEGORY_MOVIE
SportsCATEGORY_SPORTS
NewsCATEGORY_NEWS
Children/KidsCATEGORY_KIDS
MusicCATEGORY_MUSIC
EducationCATEGORY_EDUCATION
Series/EpisodeCATEGORY_SERIES
(default)CATEGORY_OTHER

Error Handling

  • 401 Unauthorized — Token invalid/expired; factory will attempt re-fetch
  • Empty schedule data — Returns ['imported' => 0, 'errors' => 0]
  • Partial failures — Individual program failures counted in errors
  • Network failures — Logged and returned as errors

Rate Limiting

SD recommends limiting requests to ~1 per second for bulk operations. The client does not implement rate limiting; stagger sync calls if needed.

Token Refresh

Tokens are cached at token_cache_path as JSON:

json
{
  "token": "abcdef...",
  "cached_at": 1705334400,
  "expires_at": 1705420800
}

The expires_at is set to 23 hours from fetch time. The factory will automatically re-fetch when the cached token expires.

Troubleshooting

"No SD token available"

  1. Verify credentials in config/livetv.php
  2. Check token_cache_path is writable
  3. Verify network connectivity to api.schedulesdirect.tmsglobal.com

"No stations found for SD lineup"

  1. Verify lineup ID is correct for your region
  2. Try null lineup_id to auto-detect available lineups
  3. Check SD account has active subscription

Empty guide data after sync

  1. Verify stations were imported (check livetv_channels table)
  2. Confirm tuner_id prefix is sd_ for SD channels
  3. Check sync returned imported > 0

Security Considerations

  • Store SD password securely (encrypted at rest in production)
  • Token cache file should be readable only by the application
  • Tokens grant access to SD data; treat similarly to API keys

BSD-3-Clause