372 日記 | 複数RSSで過去3日分を古い順に再生 episode artwork

EPISODE · Jul 2, 2026 · 12 MIN

372 日記 | 複数RSSで過去3日分を古い順に再生

from TanaRadio · host たな

複数RSSで過去3日分を古い順に再生できるようになりまいした(フェーズ5C完了)。*NHKの番組を再生している部分はカットしました。フェーズ5C 詳細マニュアル 複数RSSから「過去3日分・古い順」のプレイリストを作る 0. 今回の目標 5Cでやることは、これです。 複数のRSS URLを feeds.txt に書く ↓ PythonでRSSを順番に読む ↓ 各RSSからエピソードを集める ↓ 過去3日分だけに絞る ↓ 公開日時の古い順に並べる ↓ playlist.m3u を作る ↓ mpvで連続再生する 今回も、まだやらないことがあります。 棚選び 再生モード選び LED表示 追加ボタン ロータリーエンコーダー 再生履歴 ここ、大事です。 5Cは「複数RSS化だけ」です。まだラジオの操作部品は増やしません。焦らないのが勝ち筋です。 ステップ1:作業フォルダへ移動する Raspberry Piでターミナルを開きます。 cd ~/tanaradio5 確認します。 pwd 次のような表示ならOKです。 /home/pi/tanaradio5 ユーザー名が違う場合は、pi の部分は別名になります。 ステップ2:5Bまでのファイルを確認する ls おそらく、次のようなファイルがあるはずです。 feed.txt make_playlist_5a.py make_playlist_5b.py playlist.m3u 5Cでは、5Bのファイルを壊さず、新しく次の2つを作ります。 feeds.txt make_playlist_5c.py 5Bはそのまま残します。 動いたものは残す。これは電子工作でもプログラムでも鉄則です。動いたものを消すと、あとで泣きます。小さく泣くならまだしも、Linux相手だとわりと本気で泣きます。 ステップ3:複数RSS用の feeds.txt を作る まず、RSS URLを複数書くためのファイルを作ります。 nano feeds.txt 中には、RSS URLを1行に1本ずつ書きます。 例です。 https://listen.style/p/xxxxx/rss https://listen.style/p/yyyyy/rss https://listen.style/p/zzzzz/rss 最初は欲張らず、まずは2本でよいです。 https://listen.style/p/xxxxx/rss https://listen.style/p/yyyyy/rss 保存します。 Ctrl + O Enter Ctrl + X 確認します。 cat feeds.txt RSS URLが複数行で表示されればOKです。 ステップ4:5C用のPythonスクリプトを作る 新しいファイルを作ります。 nano make_playlist_5c.py 次のコードをそのまま貼り付けてください。 #!/usr/bin/env python3 import urllib.request import xml.etree.ElementTree as ET from pathlib import Path from datetime import datetime, timedelta, timezone from email.utils import parsedate_to_datetime from zoneinfo import ZoneInfo FEEDS_FILE = "feeds.txt" PLAYLIST_FILE = "playlist.m3u" # 5Bと同じく「過去3日分」 DAYS = 3 JST = ZoneInfo("Asia/Tokyo") def read_feed_urls(): path = Path(FEEDS_FILE) if not path.exists(): raise FileNotFoundError(f"{FEEDS_FILE} が見つかりません。") urls = [] for line in path.read_text(encoding="utf-8").splitlines(): line = line.strip() # 空行と # で始まるコメント行は無視する if not line: continue if line.startswith("#"): continue urls.append(line) if not urls: raise ValueError(f"{FEEDS_FILE} にRSS URLが書かれていません。") return urls def download_rss(url): print(f"RSSを取得します: {url}") request = urllib.request.Request( url, headers={ "User-Agent": "TanaRadio-Pi/5C" } ) with urllib.request.urlopen(request, timeout=20) as response: return response.read() def get_text(element, tag_name): child = element.find(tag_name) if child is not None and child.text: return child.text.strip() return "" def find_audio_url(item): # 通常のPodcast RSSでは enclosure に音声URLが入る enclosure = item.find("enclosure") if enclosure is not None: audio_url = enclosure.attrib.get("url") if audio_url: return audio_url # 念のため media:content 的な形式にも軽く対応する for child in item: if child.tag.endswith("content"): audio_url = child.attrib.get("url") if audio_url: return audio_url return None def parse_pub_date(item): pub_date_text = get_text(item, "pubDate") if not pub_date_text: return None try: dt = parsedate_to_datetime(pub_date_text) # タイムゾーン情報がない場合はUTC扱いにする if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) return dt.astimezone(JST) except Exception: return None def parse_rss(rss_data, feed_url): root = ET.fromstring(rss_data) channel = root.find("channel") if channel is None: raise ValueError("RSSのchannelが見つかりません。") program_title = get_text(channel, "title") if not program_title: program_title = "番組名不明" items = channel.findall("item") episodes = [] for item in items: episode_title = get_text(item, "title") audio_url = find_audio_url(item) pub_dt = parse_pub_date(item) if not audio_url: continue if pub_dt is None: continue episodes.append({ "program_title": program_title, "episode_title": episode_title, "audio_url": audio_url, "pub_dt": pub_dt, "feed_url": feed_url, }) return episodes def collect_episodes(feed_urls): all_episodes = [] for url in feed_urls: try: rss_data = download_rss(url) episodes = parse_rss(rss_data, url) all_episodes.extend(episodes) print(f" 取得できたエピソード数: {len(episodes)} 件") except Exception as e: print(" このRSSの取得または解析でエラーが発生しました。") print(f" {e}") print(" 次のRSSへ進みます。") return all_episodes def filter_recent_episodes(episodes): now = datetime.now(JST) cutoff = now - timedelta(days=DAYS) recent = [] for ep in episodes: if ep["pub_dt"] >= cutoff: recent.append(ep) return recent def remove_duplicates(episodes): seen = set() unique = [] for ep in episodes: audio_url = ep["audio_url"] if audio_url in seen: continue seen.add(audio_url) unique.append(ep) return unique def make_playlist(episodes): if not episodes: raise ValueError( f"過去{DAYS}日分のエピソードが見つかりませんでした。" ) # 古い順に並べる episodes.sort(key=lambda ep: ep["pub_dt"]) playlist_lines = ["#EXTM3U"] for ep in episodes: pub_text = ep["pub_dt"].strftime("%Y-%m-%d %H:%M") program = ep["program_title"] title = ep["episode_title"] url = ep["audio_url"] playlist_lines.append(f"# {pub_text} / {program} / {title}") playlist_lines.append(url) Path(PLAYLIST_FILE).write_text( "\n".join(playlist_lines) + "\n", encoding="utf-8" ) print(f"{PLAYLIST_FILE} を作成しました。") print(f"プレイリスト内のエピソード数: {len(episodes)} 件") def main(): try: feed_urls = read_feed_urls() print(f"RSS URL数: {len(feed_urls)} 本") episodes = collect_episodes(feed_urls) print(f"全取得エピソード数: {len(episodes)} 件") episodes = remove_duplicates(episodes) print(f"重複除去後: {len(episodes)} 件") episodes = filter_recent_episodes(episodes) print(f"過去{DAYS}日分: {len(episodes)} 件") make_playlist(episodes) print("完了しました。") except Exception as e: print("エラーが発生しました。") print(e) if __name__ == "__main__": main() 保存します。 Ctrl + O Enter Ctrl + X ステップ5:実行権限をつける chmod +x make_playlist_5c.py ただし、実行はまず次の形でやるのがおすすめです。 python3 make_playlist_5c.py 以前のように、Pythonファイルをシェルとして実行してしまう事故を避けるためです。 python3 を前につければ、確実にPythonとして実行されます。 ステップ6:プレイリストを作る 実行します。 python3 make_playlist_5c.py うまくいくと、次のような表示になります。 RSS URL数: 2 本 RSSを取得します: https://listen.style/p/xxxxx/rss 取得できたエピソード数: 10 件 RSSを取得します: https://listen.style/p/yyyyy/rss 取得できたエピソード数: 10 件 全取得エピソード数: 20 件 重複除去後: 20 件 過去3日分: 5 件 playlist.m3u を作成しました。 プレイリスト内のエピソード数: 5 件 完了しました。 ここで見るポイントは、次の3つです。 RSS URL数 過去3日分 プレイリスト内のエピソード数 プレイリスト内のエピソード数 が1件以上なら成功です。 ステップ7:playlist.m3u の中身を確認する head -n 30 playlist.m3u 次のように表示されればOKです。 #EXTM3U # 2026-07-01 08:30 / 番組A / エピソードタイトル https://... # 2026-07-01 10:15 / 番組B / エピソードタイトル https://... ここで確認したいのは、複数番組が混ざっているかどうかです。 たとえば、 番組A 番組B 番組A 番組C のように並んでいれば、5Cらしい動きになっています。 単にRSSを順番に再生しているのではなく、複数RSSのエピソードを集めて、公開日時で並べ替えているわけです。 ここが5Cの肝です。 ステップ8:mpvで再生する mpv playlist.m3u 音が出れば成功です。 mpvの基本操作はこれです。 スペースキー:一時停止/再開 Enter:次のエピソードへ q:終了 再生される順番が、過去3日分の古い順になっているか、耳でざっくり確認してください。 厳密に確認したい場合は、playlist.m3u のコメント行を見ます。 grep '^# 20' playlist.m3u 次のように日時が古い順に並んでいればOKです。 # 2026-06-29 09:00 / ... # 2026-06-30 14:20 / ... # 2026-07-01 08:30 / ... ステップ9:RSSを少しずつ増やす 最初の2本でうまく行ったら、feeds.txt にRSSを追加します。 nano feeds.txt 例です。 # TanaRadio https://listen.style/p/xxxxx/rss # 気になる番組1 https://listen.style/p/yyyyy/rss # 気になる番組2 https://listen.style/p/zzzzz/rss このスクリプトでは、空行と # で始まる行は無視されます。 なので、コメントを書いておけます。 保存したら再実行します。 python3 make_playlist_5c.py mpv playlist.m3u RSSを増やすと、かなり「フォロー中ラジオ」感が出てきます。 この段階でもう、TanaRadio Piは単なる1番組再生機ではなくなります。 ステップ10:過去3日分が空だった場合 次のように出ることがあります。 過去3日分: 0 件 エラーが発生しました。 過去3日分のエピソードが見つかりませんでした。 これは故障ではありません。 原因は単純で、登録したRSSに最近のエピソードがないだけです。 その場合は、テスト用に日数を一時的に増やします。 nano make_playlist_5c.py この部分を探します。 DAYS = 3 一時的にこうします。 DAYS = 30 保存して、再実行します。 python3 make_playlist_5c.py これでエピソードが出てくるなら、スクリプトは正常です。 テストが終わったら、また戻します。 DAYS = 3 ステップ11:成功条件 フェーズ5Cの成功条件は、次の4つです。 1. feeds.txt にRSS URLを複数本書けた 2. make_playlist_5c.py を実行できた 3. 複数RSS由来のエピソードが playlist.m3u に入った 4. mpv playlist.m3u で連続再生できた 特に重要なのは3です。 複数RSS ↓ 1つのプレイリスト ↓ 過去3日分 ↓ 古い順 これができれば、5Cは成功です。 5Cでできるようになったこと 5Cが成功すると、TanaRadio Piはこうなります。 1つの番組を聴く装置 から、 複数番組をまとめて流す装置 に変わります。 これは地味ですが、かなり大きな変化です。 もう「ポッドキャストを1本ずつ選んで聴く」のではなく、「登録してある声が時間順に流れてくる」状態になります。 ここで、かなりラジオらしくなります。 次のフェーズ5Dでやること 次の5Dでは、フェーズ4で作った既存の3ボタンを、この新しい playlist.m3u 再生に結びつけます。 予定としては、まず単純にこうします。 再生/一時停止ボタン:pause切り替え 戻るボタン:前のエピソードへ 次へボタン:次のエピソードへ 構想メモでは戻るボタンに「1回押し・2回押し・長押し」の区別も出ていますが、5Dの最初ではまだ入れなくてよいです。 まずは「物理ボタンでプレイリストを前後できる」だけで十分です。 今回の5Cは、ひとことで言えばこうです。 複数の声を、1本の時間の流れにする。 ここまで来ると、TanaRadio Piはかなり「声の本棚」の入口に立っています。これは順調です。#声日記 #TanaRadioPi LISTENで開く

Episode metadata supplied by the publisher feed · Published Jul 2, 2026

Embed this episode

NOW PLAYING

372 日記 | 複数RSSで過去3日分を古い順に再生

0:00 12:13

No transcript for this episode yet

We transcribe on demand. Request one and we'll notify you when it's ready — usually under 10 minutes.

No similar episodes found.

No similar podcasts found.

Frequently Asked Questions

How long is this episode of TanaRadio?

This episode is 12 minutes long.

When was this TanaRadio episode published?

This episode was published on July 2, 2026.

Can I download this TanaRadio episode?

Yes. Use the download control on the episode player to save the publisher-provided media file.
URL copied to clipboard!