370 lines
19 KiB
HTML
370 lines
19 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ podcast.title }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Page Header -->
|
|
<div class="page-header">
|
|
<h1 class="page-title">{{ podcast.title }}</h1>
|
|
<div class="page-actions">
|
|
<form action="{{ url_for('podcasts.update', podcast_id=podcast.id) }}" method="post" style="display: inline;">
|
|
<button type="submit" class="btn btn-primary">Update Episodes</button>
|
|
</form>
|
|
<form action="{{ url_for('podcasts.download_all', podcast_id=podcast.id) }}" method="post" style="display: inline; margin-left: 8px;">
|
|
<button type="submit" class="btn btn-success">Download All Episodes</button>
|
|
</form>
|
|
<form action="{{ url_for('podcasts.delete', podcast_id=podcast.id) }}" method="post"
|
|
style="display: inline; margin-left: 8px;"
|
|
onsubmit="return confirm('Are you sure you want to delete this podcast?');">
|
|
<button type="submit" class="btn btn-danger">Delete Podcast</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Podcast Info -->
|
|
<div style="padding: 16px; background-color: #161b22; border-bottom: 1px solid #30363d;">
|
|
<div style="display: flex; gap: 16px;">
|
|
<div style="flex-shrink: 0;">
|
|
{% if podcast.image_url %}
|
|
<img src="{{ podcast.image_url }}" alt="{{ podcast.title }}"
|
|
style="width: 120px; height: 120px; object-fit: cover; border-radius: 6px;">
|
|
{% else %}
|
|
<div style="width: 120px; height: 120px; background-color: #21262d; border-radius: 6px; display: flex; align-items: center; justify-content: center; color: #7d8590;">
|
|
No Image
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
<div style="flex: 1;">
|
|
<h2 style="color: #f0f6fc; margin-bottom: 8px; font-size: 18px;">{{ podcast.title }}</h2>
|
|
<p style="color: #7d8590; margin-bottom: 8px; font-size: 13px;">{{ podcast.author or 'Unknown Author' }}</p>
|
|
{% if podcast.description %}
|
|
<p style="color: #c9d1d9; font-size: 12px; line-height: 1.4; margin-bottom: 12px;">{{ podcast.description[:200] }}{% if podcast.description|length > 200 %}...{% endif %}</p>
|
|
{% endif %}
|
|
<div style="display: flex; gap: 16px; font-size: 11px; color: #7d8590;">
|
|
<span>Episodes: {{ episodes|length }}</span>
|
|
<span>Last Updated: {{ podcast.last_updated.strftime('%Y-%m-%d %H:%M') if podcast.last_updated else 'Never' }}</span>
|
|
<span>Last Checked: {{ podcast.last_checked.strftime('%Y-%m-%d %H:%M') if podcast.last_checked else 'Never' }}</span>
|
|
</div>
|
|
<div style="display: flex; gap: 16px; margin-top: 8px; font-size: 11px;">
|
|
{% if podcast.feed_url %}
|
|
<a href="{{ podcast.feed_url }}" target="_blank" style="color: #58a6ff;">View RSS Feed</a>
|
|
{% endif %}
|
|
<a href="#" onclick="document.getElementById('naming-format-modal').style.display='block'; return false;" style="color: #58a6ff;">Configure Naming Format</a>
|
|
<a href="#" onclick="document.getElementById('tags-modal').style.display='block'; return false;" style="color: #58a6ff;">Manage Tags</a>
|
|
</div>
|
|
|
|
{% if podcast.tags %}
|
|
<div style="margin-top: 8px;">
|
|
<span style="font-size: 11px; color: #7d8590;">Tags: </span>
|
|
{% for tag in podcast.get_tags() %}
|
|
<a href="{{ url_for('podcasts.filter_by_tag', tag=tag) }}" class="tag-badge">{{ tag }}</a>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toolbar -->
|
|
<div class="toolbar">
|
|
<span class="toolbar-btn">{{ episodes|length }} Episodes</span>
|
|
<div style="margin-left: auto; display: flex; gap: 8px;">
|
|
<form action="{{ url_for('podcasts.verify', podcast_id=podcast.id) }}" method="post" style="display: inline-block;">
|
|
<button type="submit" class="toolbar-btn">Verify Files</button>
|
|
</form>
|
|
<button class="toolbar-btn" onclick="window.location.reload()">Refresh</button>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Episodes Table -->
|
|
<div class="content-area">
|
|
{% if episodes %}
|
|
{# Group episodes by season or year if season is not available #}
|
|
{% set seasons = {} %}
|
|
{% set season_ids = {} %}
|
|
{% set season_download_counts = {} %}
|
|
{% set season_counter = 0 %}
|
|
|
|
{% for episode in episodes %}
|
|
{% set season_key = "" %}
|
|
{% if episode.season %}
|
|
{# Use season number if available #}
|
|
{% set season_key = "Season " ~ episode.season %}
|
|
{% elif episode.published_date %}
|
|
{# Use year as season if no season number but published date is available #}
|
|
{% set season_key = episode.published_date.strftime('%Y') %}
|
|
{% else %}
|
|
{# Fallback for episodes with no season or published date #}
|
|
{% set season_key = "Unsorted Episodes" %}
|
|
{% endif %}
|
|
|
|
{# Initialize season if not exists #}
|
|
{% if season_key not in seasons %}
|
|
{% set season_counter = season_counter + 1 %}
|
|
{% set _ = seasons.update({season_key: []}) %}
|
|
{% set _ = season_ids.update({season_key: season_counter}) %}
|
|
{% set _ = season_download_counts.update({season_key: {'downloaded': 0, 'total': 0}}) %}
|
|
{% endif %}
|
|
|
|
{# Add episode to season #}
|
|
{% set _ = seasons[season_key].append(episode) %}
|
|
|
|
{# Update download counts #}
|
|
{% if episode.downloaded %}
|
|
{% set downloaded = season_download_counts[season_key]['downloaded'] + 1 %}
|
|
{% set total = season_download_counts[season_key]['total'] + 1 %}
|
|
{% else %}
|
|
{% set downloaded = season_download_counts[season_key]['downloaded'] %}
|
|
{% set total = season_download_counts[season_key]['total'] + 1 %}
|
|
{% endif %}
|
|
{% set _ = season_download_counts.update({season_key: {'downloaded': downloaded, 'total': total}}) %}
|
|
{% endfor %}
|
|
|
|
{# Display seasons in reverse order (newest first) #}
|
|
{% if seasons %}
|
|
{% for season_key, episodes_list in seasons|dictsort|reverse %}
|
|
{% set season_id = season_ids[season_key] %}
|
|
{% set download_stats = season_download_counts[season_key] %}
|
|
|
|
<div class="season-accordion">
|
|
<div class="season-header" onclick="toggleSeason()">
|
|
<h3>
|
|
{{ season_key }}
|
|
<span class="episode-count">({{ download_stats['downloaded'] }}/{{ download_stats['total'] }} episodes)</span>
|
|
</h3>
|
|
<span id="toggle-icon-season_{{ season_id }}" class="toggle-icon">▼</span>
|
|
</div>
|
|
<div id="season-season_{{ season_id }}" class="season-content">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Episode</th>
|
|
<th style="width: 100px;">Published</th>
|
|
<th style="width: 80px;">Duration</th>
|
|
<th style="width: 80px;">Status</th>
|
|
<th style="width: 120px;">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for episode in episodes_list|sort(attribute='published_date', reverse=true) %}
|
|
<tr>
|
|
<td>
|
|
<div class="cell-title">
|
|
{% if episode.episode_number %}
|
|
<span style="color: #58a6ff; font-weight: bold; margin-right: 5px;">
|
|
{% if episode.season %}
|
|
S{{ '%02d' % episode.season }}E{{ '%02d' % episode.episode_number|int if episode.episode_number|string|isdigit() else episode.episode_number }}
|
|
{% else %}
|
|
#{{ '%02d' % episode.episode_number|int if episode.episode_number|string|isdigit() else episode.episode_number }}
|
|
{% endif %}
|
|
</span>
|
|
{% endif %}
|
|
{{ episode.title }}
|
|
{% if episode.explicit %}
|
|
<span class="explicit-badge" title="Explicit Content">E</span>
|
|
{% endif %}
|
|
</div>
|
|
{% if episode.description %}
|
|
<div class="cell-secondary" style="margin-top: 4px;">
|
|
{{ episode.description[:100] }}{% if episode.description|length > 100 %}...{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</td>
|
|
<td class="cell-center">
|
|
<div class="cell-secondary">
|
|
{{ episode.published_date.strftime('%Y-%m-%d') if episode.published_date else 'Unknown' }}
|
|
</div>
|
|
</td>
|
|
<td class="cell-center">
|
|
<div class="cell-secondary">
|
|
{% if episode.duration %}
|
|
{{ (episode.duration / 60)|int }}m
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td class="cell-center">
|
|
{% if episode.downloaded %}
|
|
<span class="status-badge status-active">Downloaded</span>
|
|
{% elif episode.download_error %}
|
|
<span class="status-badge status-error" title="{{ episode.download_error }}">Error {{ episode.status_code }}</span>
|
|
{% else %}
|
|
<span class="status-badge status-pending">Available</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="cell-actions">
|
|
{% if not episode.downloaded %}
|
|
<a href="{{ url_for('podcasts.download', episode_id=episode.id) }}" class="btn btn-sm">Download</a>
|
|
{% else %}
|
|
<form action="{{ url_for('podcasts.rename_episode', episode_id=episode.id) }}" method="post" style="display: inline;">
|
|
<button type="submit" class="btn btn-sm">Rename</button>
|
|
</form>
|
|
{% endif %}
|
|
{% if episode.audio_url %}
|
|
<a href="{{ episode.audio_url }}" target="_blank" class="btn btn-sm btn-secondary" style="margin-left: 4px;">Stream</a>
|
|
{% endif %}
|
|
{% if episode.download_error %}
|
|
<span class="tooltip-icon" title="{{ episode.download_error }}">ⓘ</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
{# Display episodes in a flat table if no season information is available #}
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Episode</th>
|
|
<th style="width: 100px;">Published</th>
|
|
<th style="width: 80px;">Duration</th>
|
|
<th style="width: 80px;">Status</th>
|
|
<th style="width: 120px;">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for episode in episodes %}
|
|
<tr>
|
|
<td>
|
|
<div class="cell-title">
|
|
{% if episode.episode_number %}
|
|
<span style="color: #58a6ff; font-weight: bold; margin-right: 5px;">#{{ '%02d' % episode.episode_number|int if episode.episode_number|string|isdigit() else episode.episode_number }}</span>
|
|
{% endif %}
|
|
{{ episode.title }}
|
|
{% if episode.explicit %}
|
|
<span class="explicit-badge" title="Explicit Content">E</span>
|
|
{% endif %}
|
|
</div>
|
|
{% if episode.description %}
|
|
<div class="cell-secondary" style="margin-top: 4px;">
|
|
{{ episode.description[:100] }}{% if episode.description|length > 100 %}...{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</td>
|
|
<td class="cell-center">
|
|
<div class="cell-secondary">
|
|
{{ episode.published_date.strftime('%Y-%m-%d') if episode.published_date else 'Unknown' }}
|
|
</div>
|
|
</td>
|
|
<td class="cell-center">
|
|
<div class="cell-secondary">
|
|
{% if episode.duration %}
|
|
{{ (episode.duration / 60)|int }}m
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td class="cell-center">
|
|
{% if episode.downloaded %}
|
|
<span class="status-badge status-active">Downloaded</span>
|
|
{% elif episode.download_error %}
|
|
<span class="status-badge status-error" title="{{ episode.download_error }}">Error {{ episode.status_code }}</span>
|
|
{% else %}
|
|
<span class="status-badge status-pending">Available</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="cell-actions">
|
|
{% if not episode.downloaded %}
|
|
<a href="{{ url_for('podcasts.download', episode_id=episode.id) }}" class="btn btn-sm">Download</a>
|
|
{% else %}
|
|
<form action="{{ url_for('podcasts.rename_episode', episode_id=episode.id) }}" method="post" style="display: inline;">
|
|
<button type="submit" class="btn btn-sm">Rename</button>
|
|
</form>
|
|
{% endif %}
|
|
{% if episode.audio_url %}
|
|
<a href="{{ episode.audio_url }}" target="_blank" class="btn btn-sm btn-secondary" style="margin-left: 4px;">Stream</a>
|
|
{% endif %}
|
|
{% if episode.download_error %}
|
|
<span class="tooltip-icon" title="{{ episode.download_error }}">ⓘ</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% endif %}
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<h3>No Episodes Found</h3>
|
|
<p>No episodes found for this podcast.</p>
|
|
<form action="{{ url_for('podcasts.update', podcast_id=podcast.id) }}" method="post">
|
|
<button type="submit" class="btn btn-primary">Try Updating Episodes</button>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<style>
|
|
.status-error {
|
|
background-color: #f85149;
|
|
color: white;
|
|
}
|
|
|
|
.tooltip-icon {
|
|
display: inline-block;
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 50%;
|
|
background-color: #58a6ff;
|
|
color: white;
|
|
text-align: center;
|
|
line-height: 16px;
|
|
font-size: 12px;
|
|
margin-left: 4px;
|
|
cursor: help;
|
|
}
|
|
</style>
|
|
<script>
|
|
function toggleSeason(seasonId) {
|
|
// Find the clicked header element
|
|
const clickedHeader = event.currentTarget;
|
|
|
|
// Find the content and toggle icon elements
|
|
const seasonContent = clickedHeader.nextElementSibling;
|
|
const toggleIcon = clickedHeader.querySelector('.toggle-icon');
|
|
|
|
if (seasonContent.style.display === 'block') {
|
|
// If already open, close it
|
|
seasonContent.style.display = 'none';
|
|
toggleIcon.innerHTML = '▼';
|
|
} else {
|
|
// Close all other accordions first
|
|
const allSeasonContents = document.querySelectorAll('.season-content');
|
|
const allToggleIcons = document.querySelectorAll('.toggle-icon');
|
|
|
|
allSeasonContents.forEach(function(content) {
|
|
content.style.display = 'none';
|
|
});
|
|
|
|
allToggleIcons.forEach(function(icon) {
|
|
icon.innerHTML = '▼';
|
|
});
|
|
|
|
// Then open the clicked one
|
|
seasonContent.style.display = 'block';
|
|
toggleIcon.innerHTML = '▲';
|
|
}
|
|
}
|
|
|
|
// Initialize all season accordions as collapsed by default
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Make sure all season contents have display style set to none (collapsed)
|
|
const allSeasonContents = document.querySelectorAll('.season-content');
|
|
allSeasonContents.forEach(function(content) {
|
|
content.style.display = 'none';
|
|
});
|
|
});
|
|
</script>
|
|
|
|
{% include 'podcasts/naming_format_modal.html' %}
|
|
{% include 'podcasts/tags_modal.html' %}
|
|
{% endblock %}
|