Add podgrab featureset

This commit is contained in:
Cody Cook 2025-06-16 23:07:36 -07:00
commit 4527504c80
5 changed files with 71 additions and 11 deletions

View file

@ -110,6 +110,8 @@ class Episode(db.Model):
downloaded = db.Column(db.Boolean, default=False) downloaded = db.Column(db.Boolean, default=False)
file_path = db.Column(db.String(512)) file_path = db.Column(db.String(512))
explicit = db.Column(db.Boolean, nullable=True) # Whether the episode is marked as explicit explicit = db.Column(db.Boolean, nullable=True) # Whether the episode is marked as explicit
download_error = db.Column(db.String(255), nullable=True) # Error message if download failed
status_code = db.Column(db.Integer, nullable=True) # HTTP status code from last download attempt
def __repr__(self): def __repr__(self):
return f'<Episode {self.title}>' return f'<Episode {self.title}>'
@ -133,5 +135,7 @@ class Episode(db.Model):
'guid': self.guid, 'guid': self.guid,
'downloaded': self.downloaded, 'downloaded': self.downloaded,
'file_path': self.file_path, 'file_path': self.file_path,
'explicit': self.explicit 'explicit': self.explicit,
'download_error': self.download_error,
'status_code': self.status_code
} }

View file

@ -351,21 +351,29 @@ def get_podcast_episodes(feed_url):
# Check if the URL is accessible # Check if the URL is accessible
if head_response.status_code >= 400: if head_response.status_code >= 400:
logger.warning(f"Audio URL returned status code {head_response.status_code}: {episode['audio_url']}") logger.warning(f"Audio URL returned status code {head_response.status_code}: {episode['audio_url']}")
continue # Instead of skipping, add the episode with error information
episode['download_error'] = f"Server returned status code {head_response.status_code}"
episode['status_code'] = head_response.status_code
else:
# Check if the content type is audio
content_type = head_response.headers.get('Content-Type', '')
if not content_type.startswith('audio/') and 'application/octet-stream' not in content_type:
logger.warning(f"Audio URL has non-audio content type: {content_type}")
# Don't skip here as some servers might not report the correct content type
episode['download_error'] = f"Non-audio content type: {content_type}"
else:
# If we got here, the audio URL is valid with no issues
episode['download_error'] = None
episode['status_code'] = head_response.status_code
# Check if the content type is audio # Add the episode regardless of status code
content_type = head_response.headers.get('Content-Type', '')
if not content_type.startswith('audio/') and 'application/octet-stream' not in content_type:
logger.warning(f"Audio URL has non-audio content type: {content_type}")
# Don't skip here as some servers might not report the correct content type
# If we got here, the audio URL is probably valid
episodes.append(episode) episodes.append(episode)
logger.debug(f"Added episode with valid audio URL: {episode['title']}") logger.debug(f"Added episode: {episode['title']} (Status: {episode.get('status_code')})")
except Exception as e: except Exception as e:
# If we can't validate the URL, still add the episode but log a warning # If we can't validate the URL, still add the episode but log a warning
logger.warning(f"Could not validate audio URL: {str(e)}") logger.warning(f"Could not validate audio URL: {str(e)}")
episode['download_error'] = f"Could not validate URL: {str(e)}"
episodes.append(episode) episodes.append(episode)
logger.debug(f"Added episode with unvalidated audio URL: {episode['title']}") logger.debug(f"Added episode with unvalidated audio URL: {episode['title']}")
else: else:

View file

@ -79,6 +79,16 @@ def update_podcast(podcast_id, progress_callback=None):
if not episodes: if not episodes:
logger.warning(f"No episodes found for podcast: {podcast.title}") logger.warning(f"No episodes found for podcast: {podcast.title}")
stats['feed_status'] = 'no_episodes' stats['feed_status'] = 'no_episodes'
else:
# Check if all episodes have download errors
error_episodes = [ep for ep in episodes if ep.get('download_error')]
if len(error_episodes) == len(episodes):
logger.warning(f"All {len(episodes)} episodes have download errors for podcast: {podcast.title}")
stats['feed_status'] = 'all_episodes_have_errors'
# Store the most common error for reporting
if error_episodes:
stats['error_message'] = error_episodes[0].get('download_error', 'Unknown error')
stats['status_code'] = error_episodes[0].get('status_code')
# Check if we need to refresh the feed URL from iTunes # Check if we need to refresh the feed URL from iTunes
if podcast.external_id: if podcast.external_id:
@ -132,7 +142,9 @@ def update_podcast(podcast_id, progress_callback=None):
episode_number=episode_data.get('episode_number'), episode_number=episode_data.get('episode_number'),
guid=episode_data['guid'], guid=episode_data['guid'],
downloaded=False, downloaded=False,
explicit=episode_data.get('explicit') # Explicit flag explicit=episode_data.get('explicit'), # Explicit flag
download_error=episode_data.get('download_error'), # Error message if download failed
status_code=episode_data.get('status_code') # HTTP status code
) )
db.session.add(episode) db.session.add(episode)

View file

@ -90,6 +90,12 @@ def add(podcast_id):
if stats and stats.get('new_episodes', 0) > 0: if stats and stats.get('new_episodes', 0) > 0:
flash(f'Podcast added successfully! Found {stats["new_episodes"]} episodes.', 'success') flash(f'Podcast added successfully! Found {stats["new_episodes"]} episodes.', 'success')
elif stats and stats.get('feed_status') == 'all_episodes_have_errors':
error_msg = stats.get('error_message', 'Unknown error')
status_code = stats.get('status_code', '')
status_info = f" (Status code: {status_code})" if status_code else ""
flash(f'Podcast added successfully! Found episodes but all have download issues: {error_msg}{status_info}. You can try updating later.', 'warning')
logger.warning(f"All episodes have download errors for podcast: {podcast.title}")
else: else:
flash('Podcast added successfully! No episodes found yet. The feed might be empty or inaccessible.', 'info') flash('Podcast added successfully! No episodes found yet. The feed might be empty or inaccessible.', 'info')
logger.warning(f"No episodes found for podcast: {podcast.title}") logger.warning(f"No episodes found for podcast: {podcast.title}")

View file

@ -188,6 +188,8 @@
<td class="cell-center"> <td class="cell-center">
{% if episode.downloaded %} {% if episode.downloaded %}
<span class="status-badge status-active">Downloaded</span> <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 %} {% else %}
<span class="status-badge status-pending">Available</span> <span class="status-badge status-pending">Available</span>
{% endif %} {% endif %}
@ -203,6 +205,9 @@
{% if episode.audio_url %} {% if episode.audio_url %}
<a href="{{ episode.audio_url }}" target="_blank" class="btn btn-sm btn-secondary" style="margin-left: 4px;">Stream</a> <a href="{{ episode.audio_url }}" target="_blank" class="btn btn-sm btn-secondary" style="margin-left: 4px;">Stream</a>
{% endif %} {% endif %}
{% if episode.download_error %}
<span class="tooltip-icon" title="{{ episode.download_error }}"></span>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -259,6 +264,8 @@
<td class="cell-center"> <td class="cell-center">
{% if episode.downloaded %} {% if episode.downloaded %}
<span class="status-badge status-active">Downloaded</span> <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 %} {% else %}
<span class="status-badge status-pending">Available</span> <span class="status-badge status-pending">Available</span>
{% endif %} {% endif %}
@ -274,6 +281,9 @@
{% if episode.audio_url %} {% if episode.audio_url %}
<a href="{{ episode.audio_url }}" target="_blank" class="btn btn-sm btn-secondary" style="margin-left: 4px;">Stream</a> <a href="{{ episode.audio_url }}" target="_blank" class="btn btn-sm btn-secondary" style="margin-left: 4px;">Stream</a>
{% endif %} {% endif %}
{% if episode.download_error %}
<span class="tooltip-icon" title="{{ episode.download_error }}"></span>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -293,6 +303,26 @@
{% endblock %} {% endblock %}
{% block scripts %} {% 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> <script>
function toggleSeason(seasonId) { function toggleSeason(seasonId) {
// Find the clicked header element // Find the clicked header element