Git Repository
Mini Video Library
Mini Video Library scannt lokale Medienordner, verwaltet Metadaten und Vorschaubilder und streamt kompatible Videos direkt per HTTP an den Browser.
HTTPS
https://zanvex.de/git/mini-video-library.gitinternal/web/templates/detail.html
Zum Verzeichnis{{template "head" .}}<link rel="stylesheet" href="/static/data.css"><link rel="stylesheet" href="/static/poster.css">
<section class="detail detail-page">
<aside class="detail-media">
{{if .Video.Poster}}<div class="cover poster-cover"><img src="/thumbnail/{{.Video.ID}}?poster=1" alt="{{.Video.Title}}"></div>{{if .Video.Thumbnail}}<figure class="screenshot-preview"><figcaption>{{t "video.screenshot"}}</figcaption><img src="/thumbnail/{{.Video.ID}}" alt="{{.Video.Title}}"></figure>{{end}}{{else}}<div class="cover">{{if .Video.Thumbnail}}<img src="/thumbnail/{{.Video.ID}}" alt="{{.Video.Title}}">{{else}}<span>▶</span>{{end}}</div>{{end}}
<details class="artwork-tools"><summary>{{t "video.artwork"}}</summary><div class="artwork-actions"><form method="post" action="/api/videos/{{.Video.ID}}/thumbnail"><button>{{t "video.regenerate"}}</button></form><form method="post" enctype="multipart/form-data" action="/api/videos/{{.Video.ID}}/poster"><label>{{t "video.poster"}}<input type="file" name="poster" accept="image/jpeg,image/png,image/webp" required></label><button>{{t "video.upload"}}</button></form></div></details>
</aside>
<article class="detail-content">
<a class="back" href="/">{{t "video.back"}}</a>
<header class="detail-header"><div><p class="eyebrow">{{t "video.label"}}</p><h1>{{.Video.Title}}</h1><p class="filename">{{.Video.Filename}}</p></div><span class="compatibility">{{compat .Video}}</span></header>
<div class="actions"><a class="play" href="/watch/{{.Video.ID}}">{{t "video.play"}}</a><form method="post" action="/api/videos/{{.Video.ID}}/favorite"><button class="icon" title="{{t "video.favorite"}}">{{if .Video.Favorite}}★{{else}}☆{{end}}</button></form><button id="reveal">{{t "video.reveal"}}</button><form method="post" action="/api/videos/{{.Video.ID}}/reset"><button>{{t "video.reset"}}</button></form></div>
<dl class="media-facts"><div><dt>{{t "video.resolution"}}</dt><dd>{{.Video.Width}} × {{.Video.Height}}</dd></div><div><dt>{{t "video.codec"}}</dt><dd>{{.Video.VideoCodec}} · {{printf "%.2f" .Video.FPS}} FPS</dd></div><div><dt>{{t "video.container"}}</dt><dd>{{.Video.Container}}</dd></div><div><dt>{{t "video.duration"}}</dt><dd>{{duration .Video.Duration}}</dd></div><div><dt>{{t "video.size"}}</dt><dd>{{bytes .Video.Size}}</dd></div><div><dt>{{t "video.added"}}</dt><dd>{{date .Video.AddedAt}}</dd></div></dl>
<section class="track-card"><div><p class="eyebrow">{{t "video.media"}}</p><h2>{{t "video.tracks"}}</h2></div><div id="tracks" class="muted"></div></section>
<div class="detail-panels">
{{if .Video.ParsedSeries}}<details><summary><span>Dateinamen-Erkennung</span><small>{{.Video.ParsedSeries}}</small></summary><div class="panel-body"><dl class="anidb-facts"><div><dt>Serie</dt><dd>{{.Video.ParsedSeries}}</dd></div><div><dt>Staffel / Episode</dt><dd>{{.Video.ParsedSeason}} / {{.Video.ParsedEpisode}}</dd></div><div><dt>Release-Gruppe</dt><dd>{{.Video.ReleaseGroup}}</dd></div><div><dt>Qualität</dt><dd>{{.Video.DetectedQuality}}</dd></div></dl></div></details>{{end}}
{{if .ExternalSubtitles}}<details><summary><span>Externe Untertitel</span><small>{{len .ExternalSubtitles}} Sidecar-Dateien erkannt</small></summary><div class="panel-body"><ul class="metadata-list">{{range .ExternalSubtitles}}<li><span>{{.Language}} · {{.Format}}</span>{{.Name}}{{if not .Playable}} (nicht direkt abspielbar){{end}}</li>{{end}}</ul></div></details>{{end}}
<details><summary><span>{{t "video.online"}}</span><small>{{t "video.online_help"}}</small></summary><div class="panel-body plugin-tools"><div class="lookup-row"><input id="tmdb-query" value="{{.Video.Title}}"><button id="tmdb-search" type="button">{{t "video.tmdb"}}</button></div><div class="lookup-row"><input id="anidb-aid" type="number" min="1" placeholder="AniDB AID"><button id="anidb-search" type="button">{{t "video.anidb"}}</button></div><div id="metadata-results" class="data-list" role="status" aria-live="polite"></div></div></details>
{{with .AniDB}}<details><summary><span>Gespeicherte AniDB-Daten</span><small>AID {{.ID}} · lokal verfügbar</small></summary><div class="panel-body"><dl class="anidb-facts"><div><dt>Typ</dt><dd>{{.Type}}</dd></div><div><dt>Episoden</dt><dd>{{.Episodes}}</dd></div><div><dt>Zeitraum</dt><dd>{{.StartDate}} – {{.EndDate}}</dd></div><div><dt>Bewertung</dt><dd>{{.Ratings.Permanent.Value}} / 10</dd></div></dl>{{if .Description}}<p class="metadata-description">{{.Description}}</p>{{end}}{{if .Titles}}<h3>Titel</h3><ul class="metadata-list">{{range .Titles}}<li><span>{{.Language}} · {{.Kind}}</span>{{.Text}}</li>{{end}}</ul>{{end}}{{if .Tags}}<h3>Tags</h3><div class="tag-cloud">{{range .Tags}}<span>{{.Name}}</span>{{end}}</div>{{end}}{{if .Creators}}<h3>Mitwirkende</h3><ul class="metadata-list">{{range .Creators}}<li><span>{{.Type}}</span>{{.Name}}</li>{{end}}</ul>{{end}}</div></details>{{end}}
<details><summary><span>NFO-Datei</span><small>Lokale Metadaten importieren oder exportieren</small></summary><div class="panel-body"><p class="muted">Lesen ersetzt die lokalen Metadaten. Geschrieben wird ausschließlich nach deinem Klick.</p><div class="panel-actions"><button id="nfo-read">NFO lesen</button><button id="nfo-write">NFO schreiben</button><span id="nfo-result"></span></div></div></details>
<details><summary><span>{{t "video.edit"}}</span><small>{{t "video.title_field"}}, {{t "video.tags"}}, {{t "video.description"}}</small></summary><div class="panel-body"><form class="metadata-form" method="post" action="/api/videos/{{.Video.ID}}/metadata"><label>{{t "video.title_field"}}<input name="title" value="{{.Video.Title}}"></label><label>{{t "video.tags"}}<input name="tags" value="{{.Video.Tags}}"></label><label>{{t "video.description"}}<textarea name="description" rows="5">{{.Video.Description}}</textarea></label><button class="play">{{t "video.save"}}</button></form></div></details>
</div>
</article>
</section>
<script>const videoID={{.Video.ID}};document.getElementById('reveal').onclick=()=>fetch('/api/videos/'+videoID+'/reveal',{method:'POST'});const a={{json .Video.AudioJSON}}||[],s={{json .Video.SubtitleJSON}}||[];document.getElementById('tracks').textContent=a.map(x=>'Audio · '+(x.Language||'unbekannt')+' · '+x.Codec).concat(s.map(x=>'Untertitel · '+(x.Language||'unbekannt')+' · '+x.Codec)).join('\n')||'Keine Spuren erkannt';const results=document.getElementById('metadata-results');function resultRow(x,provider){const n=document.createElement('article'),b=document.createElement('button');n.textContent=(x.title||'Ohne Titel')+' ';b.type='button';b.textContent='Übernehmen';b.onclick=async()=>{await fetch('/api/plugins/'+provider+'/apply',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({video_id:videoID,result:x})});location.reload()};n.append(b);results.append(n)}async function metadataRequest(button,url,render){results.textContent='Metadaten werden abgerufen …';button.disabled=true;try{const r=await fetch(url);const body=await r.text();if(!r.ok)throw new Error(body.trim()||('HTTP '+r.status));render(JSON.parse(body))}catch(e){results.textContent='Fehler: '+(e.message||e)}finally{button.disabled=false}}document.getElementById('tmdb-search').onclick=function(){const q=document.getElementById('tmdb-query').value.trim();if(!q){results.textContent='Bitte einen Suchbegriff eingeben.';return}metadataRequest(this,'/api/plugins/tmdb/search?q='+encodeURIComponent(q),xs=>{results.replaceChildren();if(!xs.length)results.textContent='Keine Treffer gefunden.';else for(const x of xs)resultRow(x,'tmdb')})};document.getElementById('anidb-search').onclick=function(){const aid=document.getElementById('anidb-aid').value.trim();if(!aid||Number(aid)<1){results.textContent='Bitte eine gültige AniDB AID eingeben.';return}metadataRequest(this,'/api/plugins/anidb/'+encodeURIComponent(aid),x=>{results.replaceChildren();resultRow(x,'anidb')})};async function nfo(action){const r=await fetch('/api/plugins/nfo/'+videoID+'/'+action,{method:'POST'});document.getElementById('nfo-result').textContent=r.ok?' Erfolgreich':' '+await r.text();if(r.ok&&action==='read')location.reload()}document.getElementById('nfo-read').onclick=()=>nfo('read');document.getElementById('nfo-write').onclick=()=>nfo('write');</script>{{template "foot" .}}