Add podgrab featureset
This commit is contained in:
parent
233dd5b5c0
commit
4527504c80
5 changed files with 71 additions and 11 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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}")
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue