EPISODE · Jul 1, 2026 · 13 MIN
371 日記 | 過去3日分を古い順に再生
from TanaRadio · host たな
あるポッドキャストの過去3日分を古い順に再生できるようになりました(フェーズ5B完了)。フェーズ5B 詳細マニュアル 過去3日分だけを古い順に並べる 0. 今回の目標 5Aでは、RSSから取得したエピソードをそのまま playlist.m3u にしました。 5Bでは、そこに次の条件を加えます。 過去3日分だけを対象にする ↓ 古いエピソードから新しいエピソードへ並べる ↓ mpvで連続再生する つまり、こういう再生になります。 3日前のエピソード ↓ 2日前のエピソード ↓ 昨日のエピソード ↓ 今日のエピソード これは「最新からつまみ食い」ではなく、「数日分を朝のラジオのように順番に追いつく」ためのモードです。構想メモでも、この「3日前から古い順」は追いつき再生として位置づけられています。 今回はまだ、以下はやりません。 複数RSS 棚選び ボタン追加 LED表示 ロータリーエンコーダー 再生履歴 ここ、欲張らなくて正解です。5Bは地味ですが、ここで「時間順に声を流す」感覚が出てきます。ラジオらしさが一段増します。 ステップ1:作業フォルダに移動する Raspberry Piでターミナルを開きます。 cd ~/tanaradio5 確認します。 pwd ls 次のようなファイルが見えればOKです。 feed.txt make_playlist_5a.py playlist.m3u 5Aが成功しているので、たぶんこの状態になっているはずです。 ステップ2:5Aのファイルは残しておく 5Aのスクリプトは成功版として残します。 今回は新しく、 make_playlist_5b.py を作ります。 5Aを上書きしない方が安全です。工作でもプログラムでも、成功した状態を残すのは大事です。ここを雑にすると、あとで「昨日の自分、何をした?」となります。昨日の自分はだいたいメモを残していません。困ったものです。 ステップ3:5B用スクリプトを作る 次を入力します。 nano make_playlist_5b.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 FEED_FILE = "feed.txt" PLAYLIST_FILE = "playlist_5b.m3u" INFO_FILE = "playlist_5b_info.txt" # 5Bでは「過去3日分」を対象にする DAYS_BACK = 3 # 表示用。日本時間で確認できるようにする JST = timezone(timedelta(hours=9)) def read_feed_url(): path = Path(FEED_FILE) if not path.exists(): raise FileNotFoundError(f"{FEED_FILE} が見つかりません。") url = path.read_text(encoding="utf-8").strip() if not url: raise ValueError(f"{FEED_FILE} にRSS URLが書かれていません。") return url def download_rss(url): print(f"RSSを取得します: {url}") request = urllib.request.Request( url, headers={ "User-Agent": "TanaRadio-Pi/5B" } ) with urllib.request.urlopen(request, timeout=20) as response: return response.read() def find_audio_url(item): # 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 get_text(item, tag_name): element = item.find(tag_name) if element is not None and element.text: return element.text.strip() return "" def parse_pub_date(item): text = get_text(item, "pubDate") if not text: return None # RSSのpubDateは多くの場合、次のような形式 # Tue, 30 Jun 2026 08:00:00 +0900 # Tue, 30 Jun 2026 00:00:00 GMT try: dt = parsedate_to_datetime(text) except Exception: return None # タイムゾーン情報がない場合はUTCとして扱う if dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) # 比較しやすいようにUTCへ変換 return dt.astimezone(timezone.utc) def format_dt_jst(dt): return dt.astimezone(JST).strftime("%Y-%m-%d %H:%M") def collect_episodes(rss_data): root = ET.fromstring(rss_data) channel = root.find("channel") if channel is None: raise ValueError("RSSのchannelが見つかりません。") items = channel.findall("item") if not items: raise ValueError("RSS内にitemが見つかりません。") episodes = [] skipped_no_audio = 0 skipped_no_date = 0 for item in items: title = get_text(item, "title") audio_url = find_audio_url(item) pub_date = parse_pub_date(item) if not audio_url: skipped_no_audio += 1 continue if pub_date is None: skipped_no_date += 1 continue episodes.append({ "title": title if title else "タイトルなし", "audio_url": audio_url, "pub_date": pub_date, }) return episodes, skipped_no_audio, skipped_no_date def make_playlist(episodes, skipped_no_audio, skipped_no_date): now = datetime.now(timezone.utc) cutoff = now - timedelta(days=DAYS_BACK) selected = [ episode for episode in episodes if episode["pub_date"] >= cutoff ] # 古い順に並べる selected.sort(key=lambda episode: episode["pub_date"]) info_lines = [] info_lines.append("TanaRadio Pi フェーズ5B プレイリスト情報") info_lines.append("") info_lines.append(f"対象期間: 過去{DAYS_BACK}日分") info_lines.append(f"現在時刻: {format_dt_jst(now)} JST") info_lines.append(f"対象開始: {format_dt_jst(cutoff)} JST") info_lines.append("") info_lines.append(f"RSSから取得した音声付きエピソード数: {len(episodes)} 件") info_lines.append(f"音声URLなしでスキップ: {skipped_no_audio} 件") info_lines.append(f"公開日時なし/日時解析失敗でスキップ: {skipped_no_date} 件") info_lines.append(f"今回プレイリストに入れる数: {len(selected)} 件") info_lines.append("") info_lines.append("今回入れるエピソード:") info_lines.append("") for i, episode in enumerate(selected, start=1): info_lines.append( f"{i}. {format_dt_jst(episode['pub_date'])} JST | {episode['title']}" ) Path(INFO_FILE).write_text( "\n".join(info_lines) + "\n", encoding="utf-8" ) if not selected: raise ValueError( f"過去{DAYS_BACK}日分のエピソードが見つかりませんでした。" f"確認用に {INFO_FILE} を見てください。" ) playlist_lines = ["#EXTM3U"] for episode in selected: playlist_lines.append( f"# {format_dt_jst(episode['pub_date'])} JST | {episode['title']}" ) playlist_lines.append(episode["audio_url"]) Path(PLAYLIST_FILE).write_text( "\n".join(playlist_lines) + "\n", encoding="utf-8" ) print(f"{PLAYLIST_FILE} を作成しました。") print(f"{INFO_FILE} を作成しました。") print(f"対象エピソード数: {len(selected)} 件") def main(): try: feed_url = read_feed_url() rss_data = download_rss(feed_url) episodes, skipped_no_audio, skipped_no_date = collect_episodes(rss_data) make_playlist(episodes, skipped_no_audio, skipped_no_date) print("完了しました。") except Exception as e: print("エラーが発生しました。") print(e) if __name__ == "__main__": main() 保存します。 Ctrl + O Enter Ctrl + X ステップ4:実行できるようにする 次を入力します。 chmod +x make_playlist_5b.py ただし、前回のような「import: コマンドが見つかりません」系の事故を避けるため、今回はまず python3 で実行します。 python3 make_playlist_5b.py うまくいくと、次のように表示されます。 RSSを取得します: https://listen.style/p/xxxxx/rss playlist_5b.m3u を作成しました。 playlist_5b_info.txt を作成しました。 対象エピソード数: 3 件 完了しました。 ステップ5:作成されたファイルを確認する 次を入力します。 ls 次のようなファイルが見えればOKです。 feed.txt make_playlist_5a.py make_playlist_5b.py playlist.m3u playlist_5b.m3u playlist_5b_info.txt 今回重要なのはこの2つです。 playlist_5b.m3u playlist_5b_info.txt ステップ6:プレイリスト情報を確認する まず、確認用ファイルを開きます。 cat playlist_5b_info.txt たとえば次のような表示になります。 TanaRadio Pi フェーズ5B プレイリスト情報 対象期間: 過去3日分 現在時刻: 2026-06-30 10:20 JST 対象開始: 2026-06-27 10:20 JST RSSから取得した音声付きエピソード数: 10 件 音声URLなしでスキップ: 0 件 公開日時なし/日時解析失敗でスキップ: 0 件 今回プレイリストに入れる数: 3 件 今回入れるエピソード: 1. 2026-06-28 08:00 JST | エピソードタイトルA 2. 2026-06-29 09:00 JST | エピソードタイトルB 3. 2026-06-30 07:30 JST | エピソードタイトルC ここで見るべき点は2つです。 1. 対象開始が「約3日前」になっているか 2. エピソードが古い順に並んでいるか この2つが合っていれば、5Bの中心部分は成功です。 ステップ7:playlist_5b.m3u の中身を確認する 次を入力します。 head playlist_5b.m3u 次のように表示されればOKです。 #EXTM3U # 2026-06-28 08:00 JST | エピソードタイトルA https://... # 2026-06-29 09:00 JST | エピソードタイトルB https://... 件数も確認できます。 grep -v '^#' playlist_5b.m3u | sed '/^$/d' | wc -l これで表示される数字が、playlist_5b_info.txt の「今回プレイリストに入れる数」と同じならOKです。 ステップ8:mpvで再生する いよいよ再生します。 mpv playlist_5b.m3u 音が出れば成功です。 mpvの基本操作は5Aと同じです。 スペースキー:一時停止/再開 Enter:次へ q:終了 聴いてみて、古いエピソードから順に流れているか確認してください。 ステップ9:うまくいったら通常名にコピーする 5B版がうまく動いたら、これを通常の playlist.m3u として使えるようにしておきます。 cp playlist_5b.m3u playlist.m3u その後は、いつものように次で再生できます。 mpv playlist.m3u ただし、今後しばらくは確認しやすいので、 mpv playlist_5b.m3u のまま使っても構いません。 ステップ10:5Bの成功条件 フェーズ5Bの成功条件は、次の4つです。 1. make_playlist_5b.py を作れた 2. playlist_5b.m3u が作成された 3. playlist_5b_info.txt で「過去3日分・古い順」を確認できた 4. mpv playlist_5b.m3u で再生できた この4つができれば、5Bは成功です。 よくあるエラー 1. 過去3日分のエピソードが見つかりませんでした これは、必ずしも失敗ではありません。 原因は単純で、RSS内に本当に過去3日分のエピソードがない可能性があります。 まず確認します。 cat playlist_5b_info.txt もし「RSSから取得した音声付きエピソード数」はあるのに、「今回プレイリストに入れる数」が0なら、日付条件に合うエピソードがなかったということです。 テストだけしたい場合は、スクリプト内のここを一時的に変えます。 DAYS_BACK = 3 たとえば30日にします。 DAYS_BACK = 30 保存して、もう一度実行します。 python3 make_playlist_5b.py これで動けば、スクリプト自体は正常です。最後に DAYS_BACK = 3 に戻してください。 2. 公開日時なし/日時解析失敗でスキップ が多い RSS内の pubDate がうまく読めていない可能性があります。 LISTENの通常RSSなら、おそらく大丈夫だと思います。 ただし、別サービスのRSSでは日付形式が少し違う場合があります。 この段階では、まずLISTENの公開RSSで試すのが安全です。 3. 音声URLなしでスキップ が多い これは、RSSに音声ファイルのURLが入っていない場合です。 原因としては次が考えられます。 RSS URLが番組配信用ではない 限定配信で取得できない RSS形式が特殊 5Bではまだ公開RSSだけで進めましょう。限定配信や購入済み音声は、ここに混ぜない方がいいです。沼に足を入れるのは、長靴を履いてからです。 4. 実行するとまた import: コマンドが見つかりません と出る その場合は、次のように実行してください。 python3 make_playlist_5b.py ./make_playlist_5b.py ではなく、python3 を前につけます。 それでもダメなら、ファイルの先頭行を確認します。 head -n 3 make_playlist_5b.py 先頭がこうなっていればOKです。 #!/usr/bin/env python3 import urllib.request import xml.etree.ElementTree as ET もし #!/usr/bin/env python3 より前に余計な文字や空行がある場合は、nano で直してください。 nano make_playlist_5b.py 今回はここで止めてよいです 5Bではここまでで十分です。 RSSを読む ↓ 公開日時を見る ↓ 過去3日分だけ選ぶ ↓ 古い順に並べる ↓ mpvで再生する これができれば、次の5Cで「RSSを複数本にする」準備が整います。 ここまで来ると、TanaRadio Piはかなり「フォロー中ラジオ」の原型に近づいてきます。まだボタンもLEDも増えていませんが、中身は確実にラジオらしくなっています。#声日記 #TanaRadioPi LISTENで開く
Embed this episode
NOW PLAYING
371 日記 | 過去3日分を古い順に再生
No transcript for this episode yet
Similar Episodes
No similar episodes found.
Similar Podcasts
No similar podcasts found.