Compare commits

..

45 Commits

Author SHA1 Message Date
227910a14d Фикс check_installed_tools() и проверка конфига 2023-06-02 11:00:03 +03:00
f3d2f2e705 Микрофиксы логов 2023-06-02 10:47:32 +03:00
6ef60de9f4 Микрофиксы логов 2023-06-02 10:44:45 +03:00
97fb4c8637 [#12] Разбил функцию checkAlive. Поправил комменты. 2023-06-02 10:44:10 +03:00
7e66a0d986 [#12] Разбил функцию checkAlive 2023-06-02 10:40:52 +03:00
af6220ae3f Убрал ненужную функцию 2023-06-02 10:10:05 +03:00
8b93791077 Мелкие правки, обновление/удаление зависимостей 2023-06-02 09:52:18 +03:00
a9fa313605 [#12] Заменил open().close() на os.mknod() 2023-06-02 09:45:39 +03:00
42a06db223 Небольшая перестановка 2023-06-02 09:41:06 +03:00
caa9e3264a [#12] Сделал os.path там где это требуется 2023-06-02 00:37:43 +03:00
ded7eb2158 [#12] Сделал нормальное форматирование строк 2023-06-02 00:25:07 +03:00
d66c0dfc57 [#12] Заменил "i" на человекочитаемые переменные 2023-06-02 00:18:58 +03:00
537af2c22e [#10] Указал тип возвращаемых данных в функциях, где это возможно 2023-06-02 00:13:39 +03:00
6a8f33d5b8 Мини фикс формата сообщения 2023-06-02 00:11:00 +03:00
ffeb61bda0 [#11] Заменил colored на нативные цвета 2023-06-02 00:10:24 +03:00
bc72da118e [#4] Фикс проблемы с несуществующим/заблокированным пользователем 2023-06-01 23:35:39 +03:00
7fc6fba366 Шаблон конфига более не нужен 2023-06-01 23:29:41 +03:00
9a71e45398 Add cfg_file.ini to gitignore 2023-06-01 23:29:10 +03:00
e5f99b7137 Фикс конфига и рефактор названий (CamelCase -> snake_case) 2023-06-01 23:26:30 +03:00
d1bf307c95 Фикс конфига и рефактор названий (CamelCase -> snake_case) 2023-06-01 23:22:56 +03:00
b31f2ebcf7 Merge pull request 'Перевел конфиг на человеческий configparser' (#13) from 6_lulzette_move_cfg_to_configparser into master
Reviewed-on: #13
2023-06-01 23:07:58 +03:00
d2fc70ae4e Перевел конфиг на человеческий configparser 2023-06-01 23:06:46 +03:00
f0ad40bfdd Merge pull request 'Переход от месива из баш скриптов к нормальному питоновскому коду' (#5) from move-to-python into master
Reviewed-on: #5
2022-11-13 12:45:15 +03:00
7961509aca Перешел на другую, актуальную библиотеку твича. 2022-11-13 12:43:03 +03:00
7cb3c36759 pep-8 2022-11-13 11:31:48 +03:00
b0c0573d34 Заменил os.system на нативные аналоги 2021-09-25 23:49:08 +03:00
c38cb73e3e Потенциальный фикс против падения на 104 строке 2021-09-25 23:18:08 +03:00
3a539f3ce8 Не создавать папки несуществующих стримеров 2021-09-25 14:54:26 +03:00
fc1331e075 fix readme 2021-09-25 14:49:14 +03:00
10c36350e7 Done TODO 2021-09-25 14:26:34 +03:00
c08670d2a2 Добавил устойчивость к несуществующим/забаненым юзерам 2021-09-25 14:25:34 +03:00
dd340b8caa Добавил цвета 2021-09-23 06:27:54 +03:00
1ed28aea26 Поправил удаление старых записей 2021-09-23 06:06:17 +03:00
36d6d1135b Больше PEP8+поправил еще чучуть 2021-09-23 04:11:36 +03:00
52335dd676 Доп опции ytdl пока не поддерживаются 2021-09-23 04:00:59 +03:00
b6f328b682 Поправил код в соответствии с PEP8, настроил логи 2021-09-23 03:59:05 +03:00
6e3cada45c Пофиксил автоудаление стримов 2021-08-05 00:45:31 +00:00
60a5341cdf Свои параметры к ytdl 2021-08-05 00:40:42 +00:00
ce76a97253 Путь, имя, частота опроса.
Увеличил частоту опроса до секунд, поправил лог, поправил путь для записи.
2021-07-31 11:41:35 +00:00
bc996a1430 DONE: автоматическое удаление старых стримов 2021-07-23 01:07:53 +00:00
5fbdee20a5 path fix 2021-07-22 23:28:56 +00:00
e776f8d4e4 Перенес TODO вверх, сделал глобальные переменные 2021-07-22 23:28:08 +00:00
bb4a29300b Поправил шаблон конфига 2021-07-22 23:23:03 +00:00
607f9f9b5f Немного фиксов 2021-07-22 23:20:32 +00:00
ba3b7ad2f3 Сделал скрипт демоном и чуть оптимизировал 2021-07-22 23:04:09 +00:00
5 changed files with 241 additions and 90 deletions

5
.gitignore vendored
View File

@@ -2,4 +2,7 @@ list.sh
config_python.py config_python.py
config_list.sh config_list.sh
__pycache__ __pycache__
.vscode .vscode
.idea
output.log*
cfg_file.ini

View File

@@ -1,22 +1,30 @@
# Что это? # Что это?
Скрипт который проверяет и начинает запись стримов с твича (WIP) Скрипт, который записывает стримы с твича, удаляет старые, ведет красивый лог с цветами (Alpha)
# Как юзать? # Как юзать?
* Поставить youtube-dl, python-twitch-client * Поставить youtube-dl, ffmpeg и другие пакеты из requirements.txt
`pip3 install python-twitch-client youtube-dl` `apt install youtube-dl ffmpeg -y`
* Создать файл conf_python.py и добавить свой ключ (Можно получить на https://dev.twitch.tv/console), а также переменные: `pip install -r requirements.txt`
* Создать файл conf_python.py и добавить свой ключ (Можно получить на https://dev.twitch.tv/console), а также переменные из config_python.py.template:
``` ```
twitchid="ID" # ID ключа twitchid = "ID" # ID ключа
streamers = ("jesusavgn", "252mart", "vi0xxx") # список стримеров в таком формате streamers = ("jesusavgn", "252mart", "vi0xxx") # список стримеров в таком формате
path="/путь/до/диры/со/стримами" # путь до директории, куда писать стримы path = "/путь/до/диры/со/стримами" # путь до директории, куда писать стримы
check_period = 5 # Частота проверки стримеров (в секундах)
max_files = 3 # Сколько хранить стримов
``` ```
* Добавить daemon.py в crontab, ну и офк убедиться что cron.service запущен (systemd timer не подойдет ибо он убивает child процессы после завершения работы родителя) * Запустить скрипт в screen'е или создать для него systemd.service файл (или init.d, в зависимости от системы инициализации)
`*/5 * * * * /opt/twitch-downloader/cron.sh` # Updates
## 2022-11-13
В связи с переходом на новую библиотеку необходимо указывать 2 переменные авторизации - appid и appsecret.
twitchid это appid, а секретный ключ можно получить или перегенерировать в https://dev.twitch.tv/console

View File

@@ -1,10 +0,0 @@
#!/bin/bash
###########
# just config file
###########
list="jesusavgn"
twitchid="123456789qwertyuiops"
storage_path="/media/nfs/twitch/automatedRecording"
ytdl_path="/home/ubuntu/.local/bin"
ytdl_conf="-o %(uploader)s__%(upload_date)s_%(timestamp)s__%(title)s_%(id)s.%(ext)s"

287
daemon.py
View File

@@ -1,97 +1,246 @@
#!/usr/bin/python3 #!/usr/bin/python3
# FIXME: не создавать папки для несуществующих стримеров # TODO: Перезапускать скрипт при обнаружении новой версии
# TODO: Сделать нормальную конфигурацию
# TODO: Автоматически удалять старые стримы
import os import os
import sys
from threading import Thread from threading import Thread
import json import configparser
import config_python import schedule
from twitchAPI.twitch import Twitch
import subprocess
import time
import logging
from logging.handlers import TimedRotatingFileHandler
locked_streams = list() log_format = logging.Formatter('%(asctime)s %(levelname)s - %(message)s')
log_file = 'output.log'
cfg_file = 'config.ini'
def which(command):
def get_console_handler():
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(log_format)
return console_handler
def get_file_handler():
file_handler = TimedRotatingFileHandler(log_file, when='midnight')
file_handler.setFormatter(log_format)
return file_handler
class CustomFormatter(logging.Formatter):
grey = "\x1b[38;20m"
yellow = "\x1b[33;20m"
red = "\x1b[31;20m"
bold_red = "\x1b[31;1m"
reset = "\x1b[0m"
format = "%(asctime)s %(levelname)s - %(message)s"
FORMATS = {
logging.DEBUG: grey + format + reset,
logging.INFO: grey + format + reset,
logging.WARNING: yellow + format + reset,
logging.ERROR: red + format + reset,
logging.CRITICAL: bold_red + format + reset
}
def format(self, record):
log_fmt = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_fmt)
return formatter.format(record)
def get_logger(logger_name):
"""
Инициализация лога
"""
logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG)
# Console logging
console = get_console_handler()
console.setFormatter(CustomFormatter())
logger.addHandler(console)
logger.addHandler(get_file_handler())
logger.propagate = False
return logger
def set_config():
"""
Эта функция либо читает существующий конфиг, либо создает новый.
Возвращает объект конфига (configparser.ConfigParser())
"""
config = configparser.ConfigParser()
# Читаем конфиг, если пустой - заполняем
if not config.read('cfg_file.ini'):
config["app"] = {
"path": "",
"check_period": 5,
"max_files": 3
}
config["twitch"] = {
"app_id": "",
"app_secret": "",
"streamers": "t2x2,arcadia_online,252mart,the_viox"
}
with open('cfg_file.ini', 'w') as cfg_file:
config.write(cfg_file)
# Проверка конфига
if config['twitch']['app_id'] == "" or config['twitch']['app_secret'] == "":
log.critical("Параметры app_id или app_secret пусты. Необходимо заполнить эти параметры в конфиге. "
"Читай README.md")
exit(1)
return config
def which(command) -> bool:
# Пиздец, почему нет нормального аналога which из bash??? # Пиздец, почему нет нормального аналога which из bash???
# Мой аналог отдает true или false если есть или нет утилиты command """
Мой аналог which из bash'а, который отдает true или false
при наличии или отсутствии утилиты
"""
for dirs in os.get_exec_path(): for dirs in os.get_exec_path():
if command in os.listdir(dirs): if command in os.listdir(dirs):
# Если что-нибудь нашли, то True # Если что-нибудь нашли, то True
return True return True
# Если ничего не нашли во всех дирах, то завершает функцию с False # Если ничего не нашли во всех дирах, то выходим с False
return False return False
def checkTools():
''' def check_installed_tools() -> bool:
"""
Проверяет, установлены ли необходимые утилиты Проверяет, установлены ли необходимые утилиты
''' """
tools = ('youtube-dl', 'ffmpeg') tools = ('youtube-dl', 'ffmpeg')
for i in tools: for tool in tools:
if not which(i): if not which(tool):
print(i + " не установлен") log.critical("{} не установлен".format(tool))
return False return False
return True return True
def startRecord(i):
'''
Функция, которая запускает в отдельном потоке запись стрима - recorder(i)
'''
th = Thread(target=recorder, args=(i, ))
th.start()
def recorder(i): def recorder(streamer):
''' """
Функция, которая запускает youtube-dl, фактически записывает стрим Функция, которая запускает youtube-dl, фактически записывает стрим
''' """
path = config_python.path + "/"+ i streamer_path = os.path.join(config['app']['path'], streamer)
print("Записываем стрим %s\n" % i) log.info("Записываем стрим {}".format(streamer))
# FIXME: пофиксить абсолютный путь # cmdline для запуска youtube-dl
cmdline = ["youtube-dl","https://twitch.tv/"+i] cmdline = ["youtube-dl", "-q", "-o",
import subprocess streamer_path + "/%(upload_date)s_%(title)s__%(timestamp)s_%(id)s.%(ext)s",
# Не хочу делать тут проверку на существование "youtube-dl" в $PATH "https://twitch.tv/{}".format(streamer)]
s = subprocess.call(cmdline, stdout=subprocess.DEVNULL) subprocess.call(cmdline)
print("Запись стрима %s закончена\n" % i) log.info("Запись стрима {} закончена".format(streamer))
if (os.path.exists(path+"/pid")): if os.path.exists(os.path.join(streamer_path, "pid")):
os.system("rm "+path+"/pid") os.remove(os.path.join(streamer_path, "pid"))
print("lock файл удален") log.info("lock файл удален")
def checkAlive(streamers, client_id):
''' def get_streamer_id(streamer):
1. Проверка на наличие стрима """Получаем id стримера, при неудаче отдаем None"""
1.1 Если нет - удалить lock файл, если он есть resolved_id = twitch_client.get_users(logins=[streamer])
1.2 Если есть - создать lock файл, запустить записывалку if resolved_id['data']:
''' return resolved_id['data'][0]['id']
from twitch import TwitchClient else:
client = TwitchClient(client_id=client_id) log.error(
for i in streamers: "Аккаунт {} не найден".format(streamer)
# Путь до диры со стримами )
path = config_python.path + "/"+ i return None
# Создаем путь до диры со стримером, если его нет
if not (os.path.exists(config_python.path+"/"+i)):
os.makedirs(path) def record_streamer(user_stream, streamer):
# TODO: Сделать проверку на наличие стримера """Проверяем, идет ли стрим. Если идет - записываем. Если не идет - удаляем pid файл"""
user_id=client.users.translate_usernames_to_ids(i)[0]['id'] # Получить ID по нику streamer_path = os.path.join(config['app']['path'], streamer)
# Если стрим идет, то идем дальше if user_stream['data']:
if client.streams.get_stream_by_user(user_id): # Создаем путь до диры со стримером, если папка не существует
# Если стрим идет и лок файла нет, то записываем и ставим лок if not (os.path.exists(streamer_path)):
if (client.streams.get_stream_by_user(user_id).stream_type == 'live') and not (os.path.exists(config_python.path+"/"+i+"/pid")): os.makedirs(streamer_path)
print(i+" стримит") log.info("Создана директория {}".format(streamer_path))
startRecord(i)
os.system("touch "+path+"/pid") # Если стрим идет и лок файла нет, то записываем и ставим лок
else: if (user_stream['data'][0]['type'] == 'live') and not (
print(i+" Уже стримит") os.path.exists(os.path.join(streamer_path, "pid"))):
log.info("{} стримит".format(streamer))
th = Thread(target=recorder, args=(streamer,))
th.start()
os.mknod(os.path.join(streamer_path, "pid"))
else: else:
# Если стрим не идет, то пишем об этом и убираем его из залоченных log.info(
print(i+" Не стримит") "Идет запись {}".format(streamer)
# Если есть лок, то удаляем )
if (os.path.exists(path+"/pid")): else:
os.system("rm "+path+"/pid") # Если стрим не идет, то пишем об этом и убираем его из залоченных
log.debug("{} не стримит".format(streamer))
# Если есть лок, то удаляем
if os.path.exists(os.path.join(streamer_path, "pid")):
os.remove(os.path.join(streamer_path, "pid"))
def removeOldStreams(): def streamers_loop():
"""
Цикл по стримерам
Проходится по каждому логину, достает ID стримера,
достает инфу о стримах, запускает функцию для записи
"""
for streamer in config['twitch']['streamers'].split(','):
# Достаем ID стримера, если пустой - пропускаем цикл
user_id = get_streamer_id(streamer)
if user_id is None:
continue
# Получаем данные о стриме
user_stream = twitch_client.get_streams(user_id=user_id)
# Запускаем запись
record_streamer(user_stream, streamer)
def remove_old_streams():
# https://clck.ru/WHh32 # https://clck.ru/WHh32
pass records_path = config['app']['path']
# По каждой папке со стримерами
for streamer in config['twitch']['streamers']:
try:
streamer_dir_path = os.path.join(records_path, streamer)
os.chdir(streamer_dir_path)
# Если файлов в папке со стримами больше чем указано в конфиге
if len(os.listdir(streamer_dir_path)) > int(config['app']['max_files']):
# Получаем список файлов
# и смотрим, превышает ли кол-во mp4 файлов заданное в конфиге
# Если превышает - удаляем старейший
oldest = min(os.listdir(streamer_dir_path),
key=os.path.getctime)
os.unlink(oldest)
log.warning("Удален файл: {}".format(oldest))
except Exception as e:
log.error(e)
if __name__ == "__main__": if __name__ == "__main__":
if not checkTools(): exit() # Log config
checkAlive(config_python.streamers, config_python.twitchid) log = get_logger("main")
# Проверить, установлены ли нужные утилиты
if not check_installed_tools():
exit()
# Set config
config = set_config()
# Проверять стримы раз в check_period
# Каждый час удалять старые стримы
schedule.every(int(config['app']['check_period'])).seconds.do(streamers_loop)
schedule.every(1).hours.do(remove_old_streams)
# Инициализируем клиент твича
twitch_client = Twitch(config['twitch']['app_id'], config['twitch']['app_secret'])
log.info("Запущен")
while True:
schedule.run_pending()
time.sleep(1)

View File

@@ -1,2 +1,3 @@
youtube-dl==2021.4.17 youtube-dl==2021.12.17
python-twitch-client==0.7.1 twitchAPI==2.5.7.1
schedule==1.2.0