Files
2026-07-14 00:01:48 +03:00

372 lines
18 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@extends('admin.layouts.app')
@section('title', 'Bölüm Düzenle')
@section('page-title', 'Bölüm Düzenle')
@push('styles')
<style>
.section-card { background:var(--bg2); border:1px solid var(--border); border-radius:var(--radius); margin-bottom:16px; overflow:hidden; }
.section-header { display:flex; align-items:center; gap:10px; padding:14px 20px; border-bottom:1px solid var(--border); }
.section-header h6 { font-size:13.5px; font-weight:700; color:var(--text); margin:0; }
.section-body { padding:20px; }
.stat-mini { background:var(--bg2); border:1px solid var(--border); border-radius:var(--radius); overflow:hidden; }
.stat-mini-row { display:flex; justify-content:space-between; align-items:center; padding:10px 16px; border-bottom:1px solid var(--border); font-size:13px; }
.stat-mini-row:last-child { border-bottom:none; }
</style>
@endpush
@section('content')
<div class="d-flex align-items-center gap-3 mb-4 flex-wrap">
<a href="{{ route('admin.episodes.index', ['anime_id'=>$episode->anime_id]) }}" class="btn btn-secondary btn-sm">
<i class="bi bi-arrow-left"></i> Bölümlere Dön
</a>
<div style="font-size:13px;color:var(--text2)">
Son güncelleme: {{ $episode->updated_at->format('d.m.Y H:i') }}
</div>
</div>
<form method="POST" action="{{ route('admin.episodes.update', $episode) }}" enctype="multipart/form-data">
@csrf @method('PUT')
<div class="row g-4">
<div class="col-xl-8 col-lg-7">
{{-- Bölüm Bilgileri --}}
<div class="section-card">
<div class="section-header">
<i class="bi bi-collection-play" style="color:var(--accent-lt)"></i>
<h6>Bölüm Bilgileri</h6>
</div>
<div class="section-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Anime</label>
<select name="anime_id" class="form-select" required>
@foreach($animes as $anime)
<option value="{{ $anime->id }}" {{ $episode->anime_id==$anime->id?'selected':'' }}>
{{ $anime->title }}
</option>
@endforeach
</select>
</div>
<div class="col-md-3">
<label class="form-label">Sezon</label>
<select name="season_id" class="form-select" required>
@foreach($seasons as $season)
<option value="{{ $season->id }}" {{ $episode->season_id==$season->id?'selected':'' }}>
{{ $season->season_number }}. Sezon
</option>
@endforeach
</select>
</div>
<div class="col-md-3">
<label class="form-label">Bölüm No</label>
<input type="number" name="episode_number" class="form-control"
value="{{ old('episode_number',$episode->episode_number) }}" required>
</div>
<div class="col-md-8">
<label class="form-label">Başlık</label>
<input type="text" name="title" class="form-control"
value="{{ old('title',$episode->title) }}" placeholder="Opsiyonel">
</div>
<div class="col-md-4">
<label class="form-label">Süre (saniye)</label>
<input type="number" name="duration" class="form-control"
value="{{ old('duration',$episode->duration) }}">
</div>
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-2">
<label class="form-label mb-0">Açıklama</label>
<button type="button" class="btn btn-sm btn-secondary" id="ai-ep-btn" onclick="aiGenerateEpisode()">
<i class="bi bi-stars" style="color:var(--info)"></i> AI ile Yaz
</button>
</div>
<textarea name="description" id="ep-description" class="form-control" rows="3">{{ old('description',$episode->description) }}</textarea>
<div id="ai-ep-status" class="form-text" style="display:none"></div>
</div>
</div>
</div>
</div>
{{-- İntro Ayarları --}}
<div class="section-card">
<div class="section-header">
<i class="bi bi-skip-end-fill" style="color:var(--warning)"></i>
<h6>İntro / Outro Zaman Ayarı</h6>
<span style="font-size:12px;color:var(--text2)"> "İntroyu Atla" butonu için</span>
</div>
<div class="section-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">İntro Başlangıç (sn)</label>
<input type="number" name="intro_start" class="form-control" min="0"
value="{{ old('intro_start',$episode->intro_start) }}" placeholder="örn: 5">
</div>
<div class="col-md-6">
<label class="form-label">İntro Bitiş (sn)</label>
<input type="number" name="intro_end" class="form-control" min="0"
value="{{ old('intro_end',$episode->intro_end) }}" placeholder="örn: 95">
</div>
</div>
@if($episode->intro_start && $episode->intro_end)
<div class="mt-2 p-2 rounded" style="background:var(--success-dim);border:1px solid rgba(16,185,129,.2);font-size:12.5px;color:var(--success)">
<i class="bi bi-check-circle-fill me-1"></i>
Mevcut: {{ $episode->intro_start }}sn {{ $episode->intro_end }}sn
({{ $episode->intro_end - $episode->intro_start }}sn süre)
</div>
@endif
</div>
</div>
{{-- Video Kaynağı --}}
<div class="section-card">
<div class="section-header">
<i class="bi bi-play-btn" style="color:var(--info)"></i>
<h6>Video Kaynağı</h6>
</div>
<div class="section-body">
<div class="mb-3">
<label class="form-label">Kaynak Tipi</label>
<select name="source" class="form-select">
@foreach(['bunnycdn'=>'BunnyCDN','external'=>'External URL','direct'=>'Direct M3U8'] as $v => $l)
<option value="{{ $v }}" {{ old('source',$episode->source)===$v?'selected':'' }}>{{ $l }}</option>
@endforeach
</select>
</div>
<div class="row g-3">
<div class="col-12">
<label class="form-label">Source URL</label>
<input type="text" name="source_url" class="form-control"
value="{{ old('source_url',$episode->source_url) }}"
placeholder="https://...master.m3u8">
</div>
<div class="col-md-6">
<label class="form-label">Video URL (CDN Pull)</label>
<input type="text" name="video_url" class="form-control"
value="{{ old('video_url',$episode->video_url) }}">
</div>
<div class="col-md-6">
<label class="form-label">M3U8 URL</label>
<input type="text" name="m3u8_url" class="form-control"
value="{{ old('m3u8_url',$episode->m3u8_url) }}">
</div>
</div>
</div>
</div>
{{-- İzinler --}}
@if(isset($permissions) && count($permissions))
<div class="section-card">
<div class="section-header">
<i class="bi bi-shield-lock" style="color:var(--success)"></i>
<h6>İzin Ayarları</h6>
</div>
<div class="section-body p-0">
@foreach($permissions as $perm)
@php $current = $contentPerms[$perm->key] ?? 'free'; @endphp
<div class="perm-row">
<div><div class="perm-name">{{ $perm->label }}</div></div>
<div class="perm-toggle">
<input type="radio" name="permissions[{{ $perm->key }}]" id="ep2_{{ $perm->key }}_free" value="free" {{ $current==='free'?'checked':'' }}>
<label for="ep2_{{ $perm->key }}_free" class="free">Ücretsiz</label>
<input type="radio" name="permissions[{{ $perm->key }}]" id="ep2_{{ $perm->key }}_prem" value="premium" {{ $current==='premium'?'checked':'' }}>
<label for="ep2_{{ $perm->key }}_prem" class="premium">Premium</label>
</div>
</div>
@endforeach
</div>
</div>
@endif
</div>
{{-- Sağ --}}
<div class="col-xl-4 col-lg-5">
{{-- İstatistikler --}}
<div class="section-card mb-3">
<div class="section-header">
<i class="bi bi-bar-chart" style="color:var(--info)"></i>
<h6>Bölüm İstatistikleri</h6>
</div>
<div class="section-body p-0">
<div class="stat-mini-row">
<span style="color:var(--text2)">İzlenme</span>
<span style="font-weight:700;color:var(--text)">{{ number_format($episode->view_count) }}</span>
</div>
<div class="stat-mini-row">
<span style="color:var(--text2)">Süre</span>
<span style="font-weight:600;color:var(--text)">{{ $episode->duration_formatted ?? ($episode->duration ? floor($episode->duration/60).'dk' : '—') }}</span>
</div>
<div class="stat-mini-row">
<span style="color:var(--text2)">Kaynak</span>
<span class="badge bg-secondary">{{ strtoupper($episode->source) }}</span>
</div>
<div class="stat-mini-row">
<span style="color:var(--text2)">Eklenme</span>
<span style="font-size:12px;color:var(--text)">{{ $episode->created_at->format('d.m.Y H:i') }}</span>
</div>
</div>
</div>
{{-- Thumbnail --}}
<div class="section-card">
<div class="section-header">
<i class="bi bi-image" style="color:var(--info)"></i>
<h6>Thumbnail</h6>
</div>
<div class="section-body">
@if($episode->thumbnailUrl)
<img src="{{ $episode->thumbnailUrl }}" alt=""
style="width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:var(--radius-sm);margin-bottom:12px">
@endif
<input type="file" name="thumbnail" class="form-control" accept="image/*">
<div class="form-text">Önerilen: 1280×720px</div>
</div>
</div>
{{-- Yayın --}}
<div class="section-card">
<div class="section-header">
<i class="bi bi-toggle-on" style="color:var(--success)"></i>
<h6>Yayın Ayarı</h6>
</div>
<div class="section-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<div style="font-size:13.5px;font-weight:600;color:var(--text)">Yayınla</div>
<div style="font-size:12px;color:var(--text2)">Kullanıcılara görünür yap</div>
</div>
<div class="form-check form-switch mb-0">
<input class="form-check-input" type="checkbox" name="is_published" value="1"
id="is_published" role="switch" {{ $episode->is_published?'checked':'' }}>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary w-100 btn-lg">
<i class="bi bi-floppy"></i> Değişiklikleri Kaydet
</button>
<a href="{{ route('admin.episodes.index', ['anime_id'=>$episode->anime_id]) }}"
class="btn btn-secondary w-100 mt-2">İptal</a>
</div>
</div>
</form>
{{-- Video Kaynakları + HEVC Tarama --}}
@php
$videoSources = \App\Models\VideoSource::where('episode_id', $episode->id)->orderBy('sort_order')->get();
@endphp
@if($videoSources->count())
<div class="section-card mt-4">
<div class="section-header d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center gap-2">
<i class="bi bi-collection-play" style="color:var(--warning)"></i>
<h6 class="mb-0">Video Kaynakları ({{ $videoSources->count() }})</h6>
</div>
<button type="button" class="btn btn-sm btn-outline-warning" id="hevc-scan-btn"
onclick="scanHevc({{ $episode->id }})">
<i class="bi bi-search"></i> HEVC Tara
</button>
</div>
<div class="section-body p-0">
<div id="hevc-sources-list">
@foreach($videoSources as $src)
<div class="stat-mini-row" id="src-row-{{ $src->id }}">
<div class="d-flex align-items-center gap-2">
<span class="badge bg-secondary" style="font-size:10px">{{ strtoupper($src->type) }}</span>
<span style="font-size:13px;color:var(--text)">{{ $src->label ?: '—' }}</span>
@if($src->quality)
<span style="font-size:11px;color:var(--text2)">{{ $src->quality }}</span>
@endif
</div>
<div class="d-flex align-items-center gap-2">
@if($src->is_hevc)
<span class="badge bg-danger" title="H.265 (HEVC) — Chrome'da oynatılamaz">H.265</span>
@elseif($src->hevc_checked_at)
<span class="badge bg-success" title="Tarandı: H.264 (uyumlu)">H.264 </span>
@else
<span class="badge bg-secondary" id="hevc-badge-{{ $src->id }}">Taranmadı</span>
@endif
<a href="{{ $src->url }}" target="_blank" class="btn btn-xs btn-outline-secondary" style="font-size:11px;padding:2px 6px">
<i class="bi bi-link-45deg"></i>
</a>
</div>
</div>
@endforeach
</div>
<div id="hevc-scan-status" class="px-3 py-2" style="display:none;font-size:12px;color:var(--text2)"></div>
</div>
</div>
@endif
@endsection
@push('scripts')
<script>
async function scanHevc(episodeId) {
const btn = document.getElementById('hevc-scan-btn');
const status = document.getElementById('hevc-scan-status');
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm" style="width:11px;height:11px"></span> Taranıyor...';
status.style.display = 'block';
status.textContent = 'Kaynaklar sunucu tarafında taranıyor, lütfen bekleyin...';
try {
const res = await fetch(`/admin/episodes/${episodeId}/scan-hevc`, {
method: 'POST',
headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]')?.content || '' },
});
const data = await res.json();
if (!data.ok) throw new Error('Sunucu hatası');
data.results.forEach(r => {
const badge = document.getElementById(`hevc-badge-${r.id}`);
if (badge) {
badge.className = r.is_hevc ? 'badge bg-danger' : 'badge bg-success';
badge.textContent = r.is_hevc ? 'H.265 ⚠' : 'H.264 ✓';
badge.title = r.is_hevc ? 'HEVC — Chrome desteklemiyor' : 'H.264 uyumlu';
badge.id = ''; // artık dinamik badge değil, stabil
}
});
const hevcCount = data.results.filter(r => r.is_hevc).length;
status.style.color = hevcCount > 0 ? 'var(--danger)' : 'var(--success)';
status.textContent = hevcCount > 0
? `⚠ ${hevcCount} kaynak H.265 (HEVC) — Chrome'da açılmaz!`
: '✓ Tüm kaynaklar H.264 uyumlu.';
} catch(e) {
status.style.color = 'var(--danger)';
status.textContent = '❌ Tarama başarısız: ' + e.message;
} finally {
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-search"></i> HEVC Tara';
}
}
async function aiGenerateEpisode() {
const btn = document.getElementById('ai-ep-btn');
const status = document.getElementById('ai-ep-status');
const ta = document.getElementById('ep-description');
const animeSelect = document.querySelector('[name="anime_id"]');
const animeTitle = animeSelect?.options[animeSelect.selectedIndex]?.text?.trim() || '';
const episodeNumber = document.querySelector('[name="episode_number"]')?.value?.trim();
const episodeTitle = document.querySelector('[name="title"]')?.value?.trim();
if (!animeTitle) { axToast('Anime seçili değil.', 'warning'); return; }
btn.disabled=true; btn.innerHTML='<span class="spinner-border spinner-border-sm" style="width:12px;height:12px"></span>';
status.style.display='block'; status.style.color='var(--info)'; status.textContent='AI açıklama oluşturuyor...';
try {
const res = await fetch('{{ route("admin.ai.generate") }}', {
method:'POST',
headers:{'Content-Type':'application/json','X-CSRF-TOKEN':document.querySelector('meta[name=csrf-token]')?.content||''},
body:JSON.stringify({ type:'episode', title:animeTitle, anime_title:animeTitle, episode_number:episodeNumber, episode_title:episodeTitle })
});
const data = await res.json();
if (data.error) { status.style.color='var(--danger)'; status.textContent='❌ '+data.error; }
else { ta.value=data.description; status.style.color='var(--success)'; status.textContent='✓ Açıklama oluşturuldu.'; }
} catch(e) { status.style.color='var(--danger)'; status.textContent='❌ Hata.'; }
finally { btn.disabled=false; btn.innerHTML='<i class="bi bi-stars" style="color:var(--info)"></i> AI ile Yaz'; }
}
</script>
@endpush