Написал небольшой скрипт для разграбления Youtube канала iXBT.Games и отправки аудио в телеграм. Если следишь за творчеством ребят и хочешь слушать видео и стримы в виде подкастов в телеге - подключайся https://t.me/ixbt_audio.
Наверное нет смысла заводить репу на github для одного скрипта, выложу тут как есть, может кому-то пригодится.
В каталоге со скриптом необходимо создать каталог cache/
, thumb.jpg
- изображение 320x320 для обложки к аудио и .env
файл с переменными:
# ID канала
YOUTUBE_CHANNEL=UC4e_XPBiiIO4fo4_CucxQeg
# Обрабатывать только одно видео за раз
PROCESS_ONE_VIDEO=True
# Токен телеграм-бота. Получить у @BotFather
TG_BOT_TOKEN=
# ID канала или пользователя куда отправлять сообщения
TG_CHAT_ID=
# Использовать локальный telegram bot api сервер
# для отправки файлов > 50MB
# https://github.com/tdlib/telegram-bot-api/
TG_LOCAL_MODE=True
TG_BASE_URL=http://localhost:8081/bot
TG_BASE_FILE_URL=http://localhost:8081/file/bot
Собственно код скрипта:
# coding: utf-8
import asyncio
import os
from datetime import timedelta
import requests
import scrapetube
import telegram
import yt_dlp
from dotenv import load_dotenv
from loguru import logger
load_dotenv()
channel_id = os.getenv("YOUTUBE_CHANNEL") or ""
performer = "iXBT Games"
process_one_video = os.getenv("PROCESS_ONE_VIDEO", "True") == "True"
tg_bot_token = os.getenv("TG_BOT_TOKEN") or ""
tg_chat_id = os.getenv("TG_CHAT_ID") or ""
tg_local_mode = os.getenv("TG_LOCAL_MODE", "False") == "True"
tg_base_url = os.getenv("TG_BASE_URL") or ""
tg_base_file_url = os.getenv("TG_BASE_FILE_URL") or ""
# Используем scrapetube для получения списка последних N видео и стримов
def get_last_videos(channel_id, limit: int):
videos = scrapetube.get_channel(channel_id, limit=limit, content_type="videos")
streams = scrapetube.get_channel(channel_id, limit=limit, content_type="streams")
# Инвертируем списки чтобы обрабатывать в порядке добавления на канал
videos = list(videos)[::-1]
streams = list(streams)[::-1]
videos = [*videos, *streams]
videos = [
{"id": v["videoId"], "url": f"https://www.youtube.com/watch?v={v['videoId']}"}
for v in videos
]
return videos
# Собираем описание с тайм-кодами из списка глав
def chapters_to_str(chapters):
chap_array = []
for chapter in chapters:
td = timedelta(seconds=chapter["start_time"])
chap_array.append(f"{td} - {chapter['title']}")
return "\n".join(chap_array)
logger.info("Trying to get links for 5 last videos and streams")
try:
videos = get_last_videos(channel_id, 5)
except Exception as e:
logger.error(f"Can`t get video urls: {e}")
exit(0)
# Настройки для yt_dlp
logger.info("Init youtube downloader")
ydl_opts = {
"format": "m4a/bestaudio/worst",
"outtmpl": "cache/%(id)s.%(ext)s",
"keepvideo": False,
"noplaylist": True,
"continue_dl": True,
"verbose": False,
"quiet": True,
"noprogress": True,
}
ydl = yt_dlp.YoutubeDL(ydl_opts)
# Инициализируем телеграм бота
logger.info("Init telegram bot")
if tg_local_mode:
bot = telegram.Bot(
token=tg_bot_token,
base_url=tg_base_url,
base_file_url=tg_base_file_url,
local_mode=True,
)
else:
bot = telegram.Bot(token=tg_bot_token)
# Ф-я отправки обложки и аудио с тайм-кодами (если они есть)
async def send(chat_id, v_info):
id = v_info["id"]
title = v_info["title"]
duration = v_info["duration"]
performer = v_info["performer"]
msg = ""
if v_info.get("chapters", None):
msg = chapters_to_str(v_info["chapters"])
logger.info("sending thumbnail")
message = await bot.send_photo(
chat_id=chat_id,
photo=open(f"cache/{id}.jpg", "rb"),
disable_notification=True,
) # pyright: ignore
logger.info("sending audio")
with open(f"cache/{id}.m4a", "rb") as audio_file, open(
"thumb.jpg", "rb"
) as thumbnail_file:
message = await bot.send_audio(
chat_id=chat_id,
reply_to_message_id=message.message_id,
duration=duration,
audio=audio_file,
thumbnail=thumbnail_file,
title=title,
performer=performer,
read_timeout=120,
write_timeout=120,
caption=msg,
) # pyright: ignore
pass
for video in videos:
video_id, video_url = video["id"], video["url"]
# Пропускаем видео если есть файл-метка в папке cache
if os.path.exists(f"cache/{video_id}"):
continue
# Пробуем извлечь информацию о видео по ссылке
# Пропускаем видео если не получилось
try:
logger.info("Extract info for: {}", video_id)
video_info = ydl.extract_info(video_url, download=False)
video_info["performer"] = performer
except Exception as e:
logger.info("Can`t get info for {}: {}", video_id, e)
continue
# Пропускаем стримы которые еще в эфире
if video_info.get("is_live", None):
continue
# Скачиваем обложку в cache
response = requests.get(video_info["thumbnail"])
if response.status_code == 200:
with open(f"cache/{video_id}.jpg", "wb") as f:
f.write(response.content)
else:
logger.warning("Can't get thumbnail for: {}", video_id)
continue
# Скачиваем аудио поток
logger.info("Downloading: {}", video_id)
ydl.download([video_url])
# Отправляем сообщения в телеграм
logger.info("Sending telegram message")
asyncio.run(send(chat_id=tg_chat_id, v_info=video_info))
# Если дошли до этого места - считаем, что всё хорошо
# Создаём файл-метку, которая означает, что видео обработано
open(f"cache/{video_id}", "w").close()
# Удаляем обложку и аудио
os.remove(f"cache/{video_id}.jpg")
os.remove(f"cache/{video_id}.m4a")
# Выходим если обрабатываем одно видео за один запуск скрипта
if process_one_video:
break
Для запуска я использую cron и shell-скрипт который запускает python в venv окружении. Используем flock, чтобы две копии скрипта не запустились одновременно и не начали скачивать одно и то же видео, в случае, если скачивание затянется.
#!/bin/sh
cd <путь к каталогу со скриптом>
flock -n /tmp/ixbt_bot.lock venv/bin/python ixbt_bot.py
Пишите если есть вопросы или предложения.