今日は参院選挙。投票はしますが、あまりにも暑くてまだ投票していません。
涼しい場所で涼みながらすることもないので、chatGPT と IPTV をもう一度挑戦してみました。
あるサイトのプレイリストは、
=== 2025-07-20 03:10:16 UTC ===2025/07/20/03/09/56-01001.ts2025/07/20/03/09/57-01001.ts2025/07/20/03/09/58-01001.ts2025/07/20/03/09/59-01001.ts
4 つしか表示されていません。
これまでは、56-01001.ts をダウンロードする python を作成して cron で 10 秒ごとに実行していましたが、それだとある瞬間から保存が止まってしまうことに気づきました。
ログを確認すると
🟡 Skip (already exists): 03-01001.ts🟡 Skip (already exists): 04-01001.ts🟡 Skip (already exists): 05-01001.ts🟡 Skip (already exists): 06-01001.ts
すでに ts ファイルが存在しているのでスキップされているようです。 ちょっと調べて、ファイル名を使いまわしていることがわかりました。
そうして、数秒間隔でプレイリストを更新してるのだと思います。
ts ファイル名そのものは重複していても、その前の日付・時間と思われる文字列まで含めると、おそらくユニークになると思いました。
つまり、
2025/07/20/03/09/56-01001.ts
この文字列はユニークだと思うので、この文字列を使って以下のようなファイル名で temp フォルダに保存します。
2025_07_20_03_09_56-01001.ts
cron で数秒ごとに python を実行するのでプレイリストに同じファイルがあった場合スキップするようにして、なかった場合は保存するようにしました。
さらに、ts ファイルをタイムスタンプで rename して ts フォルダに保存しました。
python は以下のようになっています。
import osimport requestsfrom urllib.parse import urljoinfrom datetime import datetimeimport time
# 保存先フォルダbase_dir = "/iptv/BS4K"temp_dir = os.path.join(base_dir, "temp")os.makedirs(temp_dir, exist_ok=True)
# マスタープレイリストURLmaster_url = "http://heno/index.m3u8"
try: master_res = requests.get(master_url, timeout=5) master_res.raise_for_status() master_lines = master_res.text.strip().splitlines() media_relative = next(line for line in master_lines if line and not line.startswith("#")) media_url = urljoin(master_url, media_relative)
media_res = requests.get(media_url, timeout=5) media_res.raise_for_status() media_lines = media_res.text.strip().splitlines()
for line in media_lines: if line.startswith("#") or not line.strip(): continue
ts_path_relative = line.strip() ts_url = urljoin(media_url, ts_path_relative)
# 例: 2025/07/20/00/25/52-05005.ts → 2025_07_20_00_25_52-05005.ts parts = ts_path_relative.replace(".ts", "").split("/") renamed = "_".join(parts) + ".ts" temp_path = os.path.join(temp_dir, renamed)
# tempにすでにあればスキップ if os.path.exists(temp_path): print(f" Skip: {renamed}") continue
# tsファイルダウンロード print(f" Downloading: {renamed}") ts_res = requests.get(ts_url, timeout=5) ts_res.raise_for_status()
# tempに保存 with open(temp_path, "wb") as f: f.write(ts_res.content)
# 保存用ファイル名(実行時刻ベース) timestamp_name = datetime.utcnow().strftime("%Y%m%d_%H%M%S") + ".ts" save_path = os.path.join(ts_dir, timestamp_name)
print(f" saved: {timestamp_name}")
except Exception as e: print(f" Error: {e}")
サイトによっては、ts ファイルは 1 秒間・プレイリストは 4 つなので、4 秒以下で python を実行する必要があります。
30-59 10 * * 0 for i in `seq 0 3 59`;do (sleep ${i}; python iptv.py >> iptv/log/cron.log 2>&1 ) & done;
ffmpeg で結合します。
#!/bin/bash
TS_DIR="./ts"LIST_FILE="./list.txt"
# list.txt を作成(ファイル名の昇順で)ls -1v ./ts/*.ts | awk '{printf "file '\''%s'\''\n", $0}' > ./list.txt
# ffmpeg で結合して mp4 に変換ffmpeg -f concat -safe 0 -i list.txt -c copy ./mp4/`date +%Y%m%d%H%M%S_`.mp4
一応それなりに見られますが、通信障害でフリーズするとその分のコマ落ちが発生します。
かなりの部分が脱落してしまう動画はほとんど無意味なので、もう一度 chatGPT と相談して以下のようにしてみました。
import requestsimport osfrom datetime import datetime, timezone
M3U8_URL = "http://heno/mono.m3u8"BASE_URL = "http://moheno/"SAVE_DIR = "iptv/SunArt/ts_files"LOG_FILE = "failed_downloads.log"
os.makedirs(SAVE_DIR, exist_ok=True)
def log_failure(ts_name): with open(LOG_FILE, "a") as log: now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") log.write(f"[{now}] Failed: {ts_name}\n")
def fetch_and_save_ts(ts_path): renamed = ts_path.replace("/", "_") save_path = os.path.join(SAVE_DIR, renamed)
# 既に保存済みならスキップ if os.path.exists(save_path): return
url = BASE_URL + ts_path try: response = requests.get(url, timeout=10) response.raise_for_status() with open(save_path, "wb") as f: f.write(response.content) print(f"Saved: {renamed}") except Exception as e: print(f"Error downloading {renamed}: {e}") log_failure(ts_path)
def main(): try: response = requests.get(M3U8_URL, timeout=5) response.raise_for_status() lines = response.text.strip().splitlines() ts_list = [line for line in lines if line.endswith(".ts")]
for ts in ts_list: fetch_and_save_ts(ts)
except Exception as e: print(f"Failed to fetch m3u8: {e}")
if __name__ == "__main__": main()