Unser neuerster Codeschnipsel erzeugt eine Sammlung, Anzeige und Suche von Metadaten der größten öffentlich rechtlichen Mediatheken (ARD, ZDF, 3sat, Arte). Unterstützt tägliche automatische Updates, lokale Speicherung, deduplizierte Einträge, Video-Links, Dauer, Datum und Webinterface. Ideal zu Bildungszwecken und als Entwickler Framework.
Rechtlicher Hinweis: Der bereitgestellte Codeschnipsel dient ausschließlich zu Bildungs- und Entwicklungszwecken. Die Nutzung erfolgt auf eigene Verantwortung. Dreamcodes übernimmt keine Haftung für Schäden, Rechtsverstöße oder sonstige Folgen, die durch die Verwendung dieses Codes entstehen.
Es liegt in der Verantwortung des Nutzers, die gesetzlichen Bestimmungen, Urheberrechte, Nutzungsbedingungen der Mediatheken und sonstige rechtliche Vorgaben einzuhalten. Unser Codeschnipsel lädt nur Metadaten und keine geschützten Inhalte.
<?php
declare(strict_types=1);
// ----------------------------------------
// CONFIG | Mediatheken Adressen bitte anpassen | Die jetzigen sind zur Demo
// ----------------------------------------
const LIBFILE = __DIR__ . '/library.json';
const LOGFILE = __DIR__ . '/update_log.txt';
$mediathek_urls = [
'ARD' => 'https://www.ardmediathek.de/video/tagesschau',
'ZDF' => 'https://www.zdf.de/dokumentation/terra-x',
'3sat' => 'https://www.3sat.de/wissen/kulturzeit',
'Arte' => 'https://www.arte.tv/de/videos/',
];
// ----------------------------------------
// HILFSFUNKTIONEN
// ----------------------------------------
function h(string $s): string { return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
function read_library(): array {
if (!file_exists(LIBFILE)) file_put_contents(LIBFILE, json_encode(['items'=>[]], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
$data = json_decode(file_get_contents(LIBFILE), true);
if (!is_array($data)) return ['items'=>[]];
return $data;
}
function write_library(array $data): bool {
$tmp = tempnam(sys_get_temp_dir(), 'libtmp');
if (!$tmp) return false;
file_put_contents($tmp, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
return rename($tmp, LIBFILE);
}
function add_item_to_library(array $item): array {
$lib = read_library();
$items = $lib['items'] ?? [];
foreach ($items as $exist) {
if (!empty($exist['url_website']) && $exist['url_website'] === ($item['url_website'] ?? '')) {
return ['ok'=>false, 'msg'=>'Eintrag existiert bereits'];
}
}
$items[] = $item;
$lib['items'] = $items;
$ok = write_library($lib);
return ['ok'=>$ok, 'msg'=>$ok?'Gespeichert':'Speichern fehlgeschlagen'];
}
function fetch_url(string $url, int $timeout=15): array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER=>true,
CURLOPT_TIMEOUT=>$timeout,
CURLOPT_FOLLOWLOCATION=>true,
CURLOPT_USERAGENT=>'mediathek-local/1.0',
]);
$body = curl_exec($ch);
$err = curl_errno($ch)?curl_error($ch):null;
curl_close($ch);
return ['body'=>$body,'error'=>$err];
}
// ----------------------------------------
// SPEZIELLE PARSER FÜR JEDE MEDIATHEK
// ----------------------------------------
function parse_ard(string $html, string $url): array {
$item = ['title'=>'','description'=>'','channel'=>'ARD','date'=>'','duration'=>'','url_website'=>$url,'url_video'=>'','topic'=>'','id'=>'ard_'.md5($url.random_int(1,99999))];
libxml_use_internal_errors(true);
$dom = new DOMDocument();
$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$nodes = $xpath->query("//meta[@property='og:title']/@content");
if($nodes->length>0) $item['title']=trim((string)$nodes->item(0)->nodeValue);
$nodes = $xpath->query("//meta[@property='og:description']/@content");
if($nodes->length>0) $item['description']=trim((string)$nodes->item(0)->nodeValue);
$nodes = $xpath->query("//meta[@property='video:duration']/@content");
if($nodes->length>0) $item['duration']=trim((string)$nodes->item(0)->nodeValue);
$nodes = $xpath->query("//meta[@property='og:video']/@content");
if($nodes->length>0) $item['url_video']=trim((string)$nodes->item(0)->nodeValue);
return $item;
}
function parse_zdf(string $html, string $url): array {
$item = ['title'=>'','description'=>'','channel'=>'ZDF','date'=>'','duration'=>'','url_website'=>$url,'url_video'=>'','topic'=>'','id'=>'zdf_'.md5($url.random_int(1,99999))];
libxml_use_internal_errors(true);
$dom = new DOMDocument();
$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$nodes = $xpath->query("//meta[@property='og:title']/@content");
if($nodes->length>0) $item['title']=trim((string)$nodes->item(0)->nodeValue);
$nodes = $xpath->query("//meta[@property='og:description']/@content");
if($nodes->length>0) $item['description']=trim((string)$nodes->item(0)->nodeValue);
$nodes = $xpath->query("//meta[@property='video:duration']/@content");
if($nodes->length>0) $item['duration']=trim((string)$nodes->item(0)->nodeValue);
$nodes = $xpath->query("//meta[@property='og:video']/@content");
if($nodes->length>0) $item['url_video']=trim((string)$nodes->item(0)->nodeValue);
return $item;
}
function parse_3sat(string $html, string $url): array {
$item = ['title'=>'','description'=>'','channel'=>'3sat','date'=>'','duration'=>'','url_website'=>$url,'url_video'=>'','topic'=>'','id'=>'3sat_'.md5($url.random_int(1,99999))];
libxml_use_internal_errors(true);
$dom = new DOMDocument();
$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$nodes = $xpath->query("//meta[@property='og:title']/@content");
if($nodes->length>0) $item['title']=trim((string)$nodes->item(0)->nodeValue);
$nodes = $xpath->query("//meta[@property='og:description']/@content");
if($nodes->length>0) $item['description']=trim((string)$nodes->item(0)->nodeValue);
return $item;
}
function parse_arte(string $html, string $url): array {
$item = ['title'=>'','description'=>'','channel'=>'Arte','date'=>'','duration'=>'','url_website'=>$url,'url_video'=>'','topic'=>'','id'=>'arte_'.md5($url.random_int(1,99999))];
libxml_use_internal_errors(true);
$dom = new DOMDocument();
$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$nodes = $xpath->query("//meta[@property='og:title']/@content");
if($nodes->length>0) $item['title']=trim((string)$nodes->item(0)->nodeValue);
$nodes = $xpath->query("//meta[@property='og:description']/@content");
if($nodes->length>0) $item['description']=trim((string)$nodes->item(0)->nodeValue);
return $item;
}
// Dispatcher
function parse_url_by_source(string $html, string $url, string $source): array {
switch($source) {
case 'ARD': return parse_ard($html,$url);
case 'ZDF': return parse_zdf($html,$url);
case '3sat': return parse_3sat($html,$url);
case 'Arte': return parse_arte($html,$url);
default: return ['title'=>'','description'=>'','channel'=>$source,'date'=>'','duration'=>'','url_website'=>$url,'url_video'=>'','topic'=>'','id'=>'local_'.md5($url.random_int(1,99999))];
}
}
// ----------------------------------------
// AUTOMATISCHES UPDATE
// ----------------------------------------
$log = [];
foreach($mediathek_urls as $source=>$url) {
$fetch = fetch_url($url,20);
if(!empty($fetch['error'])) {
$log[] = "Fehler beim Abruf $source: ".$fetch['error'];
continue;
}
$parsed = parse_url_by_source($fetch['body'],$url,$source);
$res = add_item_to_library($parsed);
$log[] = ($res['ok']?'Import erfolgreich: ':'Import fehlgeschlagen: ').$source.' ('.$res['msg'].')';
}
file_put_contents(LOGFILE,date('Y-m-d H:i:s')." - Update abgeschlossen\n".implode("\n",$log)."\n\n",FILE_APPEND);
// ----------------------------------------
// WEB-INTERFACE
// ----------------------------------------
$lib = read_library();
$items = $lib['items'] ?? [];
$q = trim((string)($_GET['q']??''));
$filterChannel = trim((string)($_GET['channel']??''));
$page = max(1,(int)($_GET['page']??1));
$perPage = 15;
// Filter
$results = array_filter($items,function($it) use($q,$filterChannel){
if($q!==''){
$hay = mb_strtolower(($it['title']??'')." ".($it['description']??'')." ".($it['channel']??''));
if(mb_strpos($hay,mb_strtolower($q))===false) return false;
}
if($filterChannel!==''){
if(mb_strpos(mb_strtolower($it['channel']??''),mb_strtolower($filterChannel))===false) return false;
}
return true;
});
usort($results,function($a,$b){
$ta = $a['date']??'';
$tb = $b['date']??'';
return strcmp((string)$tb,(string)$ta);
});
$total = count($results);
$pages = max(1,(int)ceil($total/$perPage));
$resultsPaged = array_slice($results,($page-1)*$perPage,$perPage);
// ----------------------------------------
// HTML
// ----------------------------------------
?>
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Dreamcodes Mediathekviewweb</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial;background:#fafafa;color:#111;margin:18px}
.container{max-width:1000px;margin:0 auto}
.header{display:flex;gap:12px;align-items:center;justify-content:space-between}
form.inline{display:flex;gap:8px;align-items:center}
input[type=text]{padding:8px;border-radius:6px;border:1px solid #ccc}
button{padding:8px 10px;border-radius:6px;border:0;background:#0078d7;color:#fff;cursor:pointer}
.result{background:#fff;border-radius:8px;padding:12px;margin:12px 0;box-shadow:0 1px 4px rgba(0,0,0,0.06)}
.meta{color:#666;font-size:0.9rem;margin-top:6px}
.badge{display:inline-block;padding:4px 8px;border-radius:999px;background:#eef; color:#036; font-weight:600}
.pager{display:flex;gap:6px;align-items:center;margin-top:12px}
.pager a{padding:6px 8px;background:#fff;border-radius:6px;border:1px solid #ddd;text-decoration:none;color:#0078d7}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Dreamcodes Mediathekviewweb</h1>
<div class="badge">Alles auf einen Blick + automatische Updates</div>
</div>
<form method="get" class="inline">
<input type="text" name="q" placeholder="Suche Titel, Sender, Beschreibung" value="<?=h($q)?>">
<input type="text" name="channel" placeholder="Sender optional" value="<?=h($filterChannel)?>">
<button type="submit">Suchen</button>
<a href="?">Zurücksetzen</a>
</form>
<div class="meta">Treffer <?=$total?>, Seite <?=$page?> von <?=$pages?></div>
<?php foreach($resultsPaged as $entry): ?>
<article class="result">
<div style="display:flex;justify-content:space-between;gap:12px">
<div style="flex:1">
<div style="font-weight:700"><?=h($entry['title']??'Untitled')?></div>
<div class="meta"><?=h($entry['channel']??'')?>, <?=h($entry['topic']??'')?></div>
<div style="margin-top:8px"><?=nl2br(h($entry['description']??''))?></div>
<div class="meta">Website: <a href="<?=h($entry['url_website']??'#')?>" target="_blank"><?=h($entry['url_website']??'')?></a></div>
<?php if(!empty($entry['url_video'])): ?>
<div class="meta">Video: <a href="<?=h($entry['url_video'])?>" target="_blank"><?=h($entry['url_video'])?></a></div>
<?php endif ?>
<div class="meta">Dauer <?=h($entry['duration']??'')?> | ID <?=h($entry['id']??'')?></div>
</div>
<div style="min-width:140px;text-align:right">
<div class="meta"><?=h($entry['date']??'')?></div>
</div>
</div>
</article>
<?php endforeach ?>
<div class="pager">
<?php if($page>1): ?><a href="?<?=http_build_query(array_merge($_GET,['page'=>$page-1]))?>">« zurück</a><?php endif?>
<?php if($page<$pages): ?><a href="?<?=http_build_query(array_merge($_GET,['page'=>$page+1]))?>">weiter »</a><?php endif?>
</div>
<footer style="margin-top:18px;color:#666;font-size:0.9rem">
© <?= date('Y') ?> <a href="https://www.dreamcodes.net" target="_blank" rel="noopener">Dreamcodes</a>.
Nutzung erfolgt auf eigene Verantwortung.
Beachten Sie Urheberrechte und rechtliche Bestimmungen der Quellseiten.
</footer>
</div>
</body>
</html>