feat: add macOS disc scanner + API ingest endpoint
- scanner/scan_disc.py: polls optical drive via drutil, detects disc type (DVD/Blu-ray/Audio CD/Data CD), reads volume label, file/track count, posts to remote API, auto-ejects. Pure Python + requests, no drivers. - scanner/requirements.txt + README.md: setup and usage docs - videodb/api_ingest.php: authenticated POST endpoint that writes disc records directly into the videoDB MySQL schema; token stored in config - docker-compose.yml: adds INGEST_API_TOKEN env var - docker-entrypoint.sh: writes ingest_api_token into config.inc.php Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
87
videodb/api_ingest.php
Normal file
87
videodb/api_ingest.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* Disc ingest API
|
||||
*
|
||||
* Called by the local macOS scanner (scanner/scan_disc.py).
|
||||
* Accepts a JSON POST with disc metadata and inserts a record
|
||||
* into the videoDB database.
|
||||
*
|
||||
* Auth: X-API-Token header must match INGEST_API_TOKEN in config.inc.php
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// ── Load config ───────────────────────────────────────────────────────────────
|
||||
$config = [];
|
||||
require_once './config.sample.php';
|
||||
if (!@include_once './config.inc.php') {
|
||||
http_response_code(503);
|
||||
echo json_encode(['error' => 'config.inc.php not found']);
|
||||
exit;
|
||||
}
|
||||
require_once './core/constants.php';
|
||||
|
||||
// ── Auth ─────────────────────────────────────────────────────────────────────
|
||||
$expected_token = $config['ingest_api_token'] ?? '';
|
||||
$provided_token = $_SERVER['HTTP_X_API_TOKEN'] ?? '';
|
||||
|
||||
if (!$expected_token || $provided_token !== $expected_token) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Method guard ─────────────────────────────────────────────────────────────
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['error' => 'POST required']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Parse body ───────────────────────────────────────────────────────────────
|
||||
$body = file_get_contents('php://input');
|
||||
$data = json_decode($body, true);
|
||||
|
||||
if (!$data || empty($data['title'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Missing required field: title']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Connect ───────────────────────────────────────────────────────────────────
|
||||
$dbh = @mysqli_connect(
|
||||
$config['db_server'],
|
||||
$config['db_user'],
|
||||
$config['db_password'],
|
||||
$config['db_database']
|
||||
);
|
||||
if (!$dbh) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'DB connection failed: ' . mysqli_connect_error()]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Sanitize inputs ───────────────────────────────────────────────────────────
|
||||
$title = mysqli_real_escape_string($dbh, substr($data['title'], 0, 255));
|
||||
$mediatype = (int)($data['mediatype'] ?? 1); // 1=DVD, 16=Blu-ray, 18=CD
|
||||
$comment = mysqli_real_escape_string($dbh, substr($data['comment'] ?? '', 0, 255));
|
||||
$filesize = (int)($data['filesize'] ?? 0);
|
||||
$custom1 = mysqli_real_escape_string($dbh, substr($data['custom1'] ?? '', 0, 255)); // raw drutil type
|
||||
$custom2 = mysqli_real_escape_string($dbh, substr($data['custom2'] ?? '', 0, 255)); // track count / file count
|
||||
$disklabel = mysqli_real_escape_string($dbh, substr($data['disklabel'] ?? '', 0, 32));
|
||||
|
||||
// ── Insert ────────────────────────────────────────────────────────────────────
|
||||
$sql = "INSERT INTO " . TBL_DATA . "
|
||||
(title, mediatype, comment, filesize, disklabel, custom1, custom2, created, owner_id)
|
||||
VALUES
|
||||
('$title', $mediatype, '$comment', $filesize, '$disklabel', '$custom1', '$custom2', NOW(), 1)";
|
||||
|
||||
if (mysqli_query($dbh, $sql)) {
|
||||
$id = (int)mysqli_insert_id($dbh);
|
||||
echo json_encode(['ok' => true, 'id' => $id, 'title' => $data['title']]);
|
||||
} else {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => 'Insert failed: ' . mysqli_error($dbh)]);
|
||||
}
|
||||
|
||||
mysqli_close($dbh);
|
||||
@@ -6,6 +6,7 @@ DB_USER="${DB_USER:-videodb}"
|
||||
DB_PASSWORD="${DB_PASSWORD:-videodb_secret}"
|
||||
DB_NAME="${DB_NAME:-videodb}"
|
||||
DB_PREFIX="${DB_PREFIX:-videodb_}"
|
||||
INGEST_API_TOKEN="${INGEST_API_TOKEN:-changeme}"
|
||||
|
||||
CONFIG_FILE="/var/www/html/config.inc.php"
|
||||
|
||||
@@ -55,6 +56,7 @@ cat > "$CONFIG_FILE" <<PHP
|
||||
\$config['xls_extra_fields'] = 'title (plot), diskid, genres, language, mediatype, runtime, year, custom1, custom2, custom3, custom4, insertdate, owner, lent';
|
||||
\$config['dvdb_user'] = '';
|
||||
\$config['dvdb_password'] = '';
|
||||
\$config['ingest_api_token'] = '${INGEST_API_TOKEN}';
|
||||
PHP
|
||||
|
||||
chown www-data:www-data "$CONFIG_FILE"
|
||||
|
||||
Reference in New Issue
Block a user