Initial commit

This commit is contained in:
Cody Cook 2025-06-14 22:53:38 -07:00
commit e86ab53de5
35 changed files with 2638 additions and 0 deletions

25
templates/about.html Normal file
View file

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}About{% endblock %}
{% block content %}
<section class="about">
<h2>About Podcastrr</h2>
<p>Podcastrr is a podcast management application similar to Sonarr but for podcasts, built with Python and Flask.</p>
<h3>Features</h3>
<ul>
<li><strong>Search for Podcasts</strong>: Find podcasts from various sources</li>
<li><strong>Track Podcasts</strong>: Monitor your favorite podcasts for new episodes</li>
<li><strong>Download Management</strong>: Automatically download new episodes and manage storage</li>
<li><strong>Custom Naming</strong>: Configure how downloaded files are named</li>
<li><strong>Web Interface</strong>: Manage everything through an intuitive web interface</li>
</ul>
<h3>Version</h3>
<p>1.0.0</p>
<h3>License</h3>
<p>MIT</p>
</section>
{% endblock %}

60
templates/base.html Normal file
View file

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ title }}{% endblock %} - Podcastrr</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
{% block extra_head %}{% endblock %}
</head>
<body>
<div class="layout-container">
<!-- Header with app name and search -->
<header class="main-header">
<div class="logo">
<h1>Podcastrr</h1>
</div>
<div class="search-container">
<form action="{{ url_for('podcasts.search') }}" method="post" class="header-search-form">
<input type="text" name="query" placeholder="Search podcasts...">
<button type="submit" class="btn btn-search">Search</button>
</form>
</div>
</header>
<!-- Left sidebar navigation -->
<nav class="sidebar">
<ul class="sidebar-nav">
<li><a href="{{ url_for('main.index') }}">Home</a></li>
<li><a href="{{ url_for('podcasts.index') }}">Podcasts</a></li>
<li><a href="{{ url_for('main.dashboard') }}">Dashboard</a></li>
<li><a href="{{ url_for('settings.index') }}">Settings</a></li>
<li><a href="{{ url_for('main.about') }}">About</a></li>
</ul>
</nav>
<!-- Main content area -->
<main class="main-content">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-messages">
{% for category, message in messages %}
<div class="flash-message {{ category }}">
{{ message }}
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</main>
</div>
<footer>
<p>&copy; 2023 Podcastrr</p>
</footer>
{% block scripts %}{% endblock %}
</body>
</html>

53
templates/dashboard.html Normal file
View file

@ -0,0 +1,53 @@
{% extends "base.html" %}
{% block title %}Dashboard{% endblock %}
{% block content %}
<section class="dashboard">
<h2>Dashboard</h2>
<div class="stats-grid">
<div class="stat-card">
<h3>Total Podcasts</h3>
<p class="stat-value">{{ total_podcasts }}</p>
</div>
<div class="stat-card">
<h3>Recent Episodes</h3>
<p class="stat-value">Coming Soon</p>
</div>
<div class="stat-card">
<h3>Downloads</h3>
<p class="stat-value">Coming Soon</p>
</div>
<div class="stat-card">
<h3>Storage Used</h3>
<p class="stat-value">Coming Soon</p>
</div>
</div>
<div class="actions">
<h3>Quick Actions</h3>
<div class="button-group">
<a href="{{ url_for('podcasts.search') }}" class="btn">Search for Podcasts</a>
<button class="btn" id="update-all">Update All Podcasts</button>
<button class="btn" id="clean-downloads">Clean Old Downloads</button>
</div>
</div>
</section>
{% endblock %}
{% block scripts %}
<script>
// Simple JavaScript for the buttons (to be implemented)
document.getElementById('update-all').addEventListener('click', function() {
alert('Update all podcasts functionality coming soon!');
});
document.getElementById('clean-downloads').addEventListener('click', function() {
alert('Clean old downloads functionality coming soon!');
});
</script>
{% endblock %}

32
templates/index.html Normal file
View file

@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block content %}
<section class="hero">
<h2>Welcome to Podcastrr</h2>
<p>A podcast management application similar to Sonarr but for podcasts.</p>
</section>
<section class="recent-podcasts">
<h3>Recent Podcasts</h3>
{% if recent_podcasts %}
<div class="podcast-grid">
{% for podcast in recent_podcasts %}
<div class="podcast-card">
{% if podcast.image_url %}
<img src="{{ podcast.image_url }}" alt="{{ podcast.title }}">
{% else %}
<div class="no-image">No Image</div>
{% endif %}
<h4>{{ podcast.title }}</h4>
<p class="author">{{ podcast.author }}</p>
<a href="{{ url_for('podcasts.view', podcast_id=podcast.id) }}" class="btn">View Episodes</a>
</div>
{% endfor %}
</div>
{% else %}
<p>No podcasts found. <a href="{{ url_for('podcasts.search') }}">Search for podcasts</a> to get started.</p>
{% endif %}
</section>
{% endblock %}

View file

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% block title %}Your Podcasts{% endblock %}
{% block content %}
<section class="podcasts">
<div class="section-header">
<h2>Your Podcasts</h2>
<a href="{{ url_for('podcasts.search') }}" class="btn">Search for Podcasts</a>
</div>
{% if podcasts %}
<div class="podcast-grid">
{% for podcast in podcasts %}
<div class="podcast-card">
{% if podcast.image_url %}
<img src="{{ podcast.image_url }}" alt="{{ podcast.title }}">
{% else %}
<div class="no-image">No Image</div>
{% endif %}
<h3>{{ podcast.title }}</h3>
<p class="author">{{ podcast.author }}</p>
<div class="podcast-actions">
<a href="{{ url_for('podcasts.view', podcast_id=podcast.id) }}" class="btn">View Episodes</a>
<form action="{{ url_for('podcasts.delete', podcast_id=podcast.id) }}" method="post" onsubmit="return confirm('Are you sure you want to delete this podcast?');">
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<p>You haven't added any podcasts yet.</p>
<a href="{{ url_for('podcasts.search') }}" class="btn">Search for Podcasts</a>
</div>
{% endif %}
</section>
{% endblock %}

View file

@ -0,0 +1,43 @@
{% extends "base.html" %}
{% block title %}Search for Podcasts{% endblock %}
{% block content %}
<section class="search">
<h2>Search for Podcasts</h2>
<form method="post" class="search-form">
<div class="form-group">
<input type="text" name="query" id="query" placeholder="Enter podcast name or keywords" required>
<button type="submit" class="btn">Search</button>
</div>
</form>
{% if results %}
<div class="search-results">
<h3>Search Results</h3>
<div class="podcast-grid">
{% for podcast in results %}
<div class="podcast-card">
{% if podcast.image_url %}
<img src="{{ podcast.image_url }}" alt="{{ podcast.title }}">
{% else %}
<div class="no-image">No Image</div>
{% endif %}
<h4>{{ podcast.title }}</h4>
<p class="author">{{ podcast.author }}</p>
<p class="genre">{{ podcast.genre }}</p>
<form action="{{ url_for('podcasts.add', podcast_id=podcast.external_id) }}" method="post">
<button type="submit" class="btn">Add to Library</button>
</form>
</div>
{% endfor %}
</div>
</div>
{% elif request.method == 'POST' %}
<div class="no-results">
<p>No podcasts found matching your search. Try different keywords.</p>
</div>
{% endif %}
</section>
{% endblock %}

View file

@ -0,0 +1,86 @@
{% extends "base.html" %}
{% block title %}{{ podcast.title }}{% endblock %}
{% block content %}
<section class="podcast-detail">
<div class="podcast-header">
<div class="podcast-image">
{% if podcast.image_url %}
<img src="{{ podcast.image_url }}" alt="{{ podcast.title }}">
{% else %}
<div class="no-image">No Image</div>
{% endif %}
</div>
<div class="podcast-info">
<h1>{{ podcast.title }}</h1>
<p class="podcast-author">{{ podcast.author }}</p>
<div class="podcast-description">
<p>{{ podcast.description }}</p>
</div>
<div class="podcast-actions">
<form action="{{ url_for('podcasts.delete', podcast_id=podcast.id) }}" method="post" onsubmit="return confirm('Are you sure you want to delete this podcast?');">
<button type="submit" class="btn btn-danger">Delete Podcast</button>
</form>
<form action="{{ url_for('podcasts.update', podcast_id=podcast.id) }}" method="post">
<button type="submit" class="btn">Update Episodes</button>
</form>
</div>
<div class="podcast-meta">
<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>
</div>
<div class="episodes-section">
<div class="episodes-header">
<h2>Episodes ({{ episodes|length }})</h2>
</div>
{% if episodes %}
<div class="episodes-list">
{% for episode in episodes %}
<div class="episode-item">
<div class="episode-header">
<h3 class="episode-title">{{ episode.title }}</h3>
<span class="episode-date">{{ episode.published_date.strftime('%Y-%m-%d') if episode.published_date else 'Unknown date' }}</span>
</div>
<div class="episode-description">
<p>{{ episode.description|truncate(200) }}</p>
</div>
<div class="episode-footer">
<div class="episode-meta">
{% if episode.duration %}
<span class="episode-duration">Duration: {{ (episode.duration / 60)|int }} min</span>
{% endif %}
{% if episode.episode_number %}
<span class="episode-number">Episode: {{ episode.episode_number }}</span>
{% endif %}
</div>
<div class="episode-actions">
{% if episode.downloaded %}
<span class="badge downloaded">Downloaded</span>
{% else %}
<a href="{{ url_for('podcasts.download', episode_id=episode.id) }}" class="btn btn-sm">Download</a>
{% endif %}
<a href="{{ episode.audio_url }}" target="_blank" class="btn btn-sm btn-secondary">Stream</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="no-episodes">
<p>No episodes found for this podcast. Try clicking the "Update Episodes" button to fetch the latest episodes.</p>
</div>
{% endif %}
</div>
</section>
{% endblock %}

View file

@ -0,0 +1,73 @@
{% extends "base.html" %}
{% block title %}Settings{% endblock %}
{% block content %}
<section class="settings">
<h2>Application Settings</h2>
<form method="post" class="settings-form">
<div class="form-group">
<label for="download_path">Download Path</label>
<input type="text" name="download_path" id="download_path" value="{{ settings.download_path }}" required>
<small>Directory where podcast episodes will be downloaded</small>
</div>
<div class="form-group">
<label for="naming_format">Naming Format</label>
<input type="text" name="naming_format" id="naming_format" value="{{ settings.naming_format }}" required>
<small>Format for downloaded files. Available variables: {podcast_title}, {episode_title}, {published_date}, {episode_number}, {author}</small>
<div id="naming-preview" class="preview">
<strong>Preview:</strong> <span id="preview-text">{{ settings.naming_format }}</span>
</div>
</div>
<div class="form-group checkbox">
<input type="checkbox" name="auto_download" id="auto_download" {% if settings.auto_download %}checked{% endif %}>
<label for="auto_download">Auto-download new episodes</label>
</div>
<div class="form-group">
<label for="max_downloads">Maximum Concurrent Downloads</label>
<input type="number" name="max_downloads" id="max_downloads" value="{{ settings.max_downloads }}" min="1" max="10" required>
</div>
<div class="form-group">
<label for="delete_after_days">Delete Episodes After (days)</label>
<input type="number" name="delete_after_days" id="delete_after_days" value="{{ settings.delete_after_days }}" min="0" required>
<small>Set to 0 to never delete</small>
</div>
<div class="form-actions">
<button type="submit" class="btn">Save Settings</button>
</div>
</form>
</section>
{% endblock %}
{% block scripts %}
<script>
// Preview naming format
const namingFormatInput = document.getElementById('naming_format');
const previewText = document.getElementById('preview-text');
namingFormatInput.addEventListener('input', function() {
// Send AJAX request to get preview
fetch('{{ url_for("settings.naming_preview") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'naming_format=' + encodeURIComponent(this.value)
})
.then(response => response.json())
.then(data => {
if (data.preview) {
previewText.textContent = data.preview;
} else if (data.error) {
previewText.textContent = 'Error: ' + data.error;
}
});
});
</script>
{% endblock %}