Compare commits

..

15 Commits

Author SHA1 Message Date
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
3 changed files with 184 additions and 143 deletions

3
.gitignore vendored
View File

@@ -4,4 +4,5 @@ config_list.sh
__pycache__ __pycache__
.vscode .vscode
.idea .idea
output.log output.log*
cfg_file.ini

View File

@@ -1,5 +0,0 @@
appid=""
appsecret=""
streamers = ("jesusavgn", "252mart", "vi0xxx")
path="/home/losted/test"
period = 5

319
daemon.py
View File

@@ -1,150 +1,21 @@
#!/usr/bin/python3 #!/usr/bin/python3
# TODO: Перезапускать скрипт при обнаружении новой версии # TODO: Перезапускать скрипт при обнаружении новой версии
# TODO: Сделать нормальную конфигурацию
import os import os
import sys import sys
from threading import Thread from threading import Thread
from types import resolve_bases import configparser
import config_python
import schedule import schedule
from twitchAPI.twitch import Twitch from twitchAPI.twitch import Twitch
import subprocess import subprocess
import time import time
import logging import logging
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
from termcolor import colored
streamers = config_python.streamers log_format = logging.Formatter('%(asctime)s %(levelname)s - %(message)s')
app_id = config_python.appid
app_secret = config_python.appsecret
log_format = logging.Formatter('%(asctime)s %(levelname)s:%(message)s')
log_file = 'output.log' log_file = 'output.log'
cfg_file = 'config.ini'
def which(command):
# Пиздец, почему нет нормального аналога which из bash???
"""
Мой аналог which из bash'а, который отдает true или false
при наличии или отсутствии утилиты
"""
for dirs in os.get_exec_path():
if command in os.listdir(dirs):
# Если что-нибудь нашли, то True
return True
# Если ничего не нашли во всех дирах, то выходим с False
return False
def checkTools():
"""
Проверяет, установлены ли необходимые утилиты
"""
tools = ('youtube-dl', 'ffmpeg')
for i in tools:
if not which(i):
log.critical(i + " не установлен")
return False
return True
def startRecord(i):
"""
Функция, которая запускает в отдельном потоке запись стрима - recorder(i)
"""
th = Thread(target=recorder, args=(i, ))
th.start()
def recorder(i):
"""
Функция, которая запускает youtube-dl, фактически записывает стрим
"""
path = config_python.path + "/" + i
log.info("Записываем стрим %s\n" % i)
# cmdline для запуска youtube-dl
cmdline = ["youtube-dl", "-q", "-o",
path+"/%(upload_date)s_%(title)s__%(timestamp)s_%(id)s.%(ext)s",
"https://twitch.tv/" + i]
subprocess.call(cmdline)
log.info("Запись стрима %s закончена\n" % i)
if os.path.exists(path + "/pid"):
os.remove(path+"/pid")
log.info("lock файл удален")
def checkAlive():
# FIXME: Распилить ну более мелкие функции
"""
1. Проверка на наличие стрима
1.1 Если нет - удалить lock файл, если он есть
1.2 Если есть - создать lock файл, запустить записывалку
"""
for i in streamers:
# Путь до диры со стримами
path = config_python.path + "/" + i
# Получаем инфо о стримере, если не получается, выходим с ошибкой
# resolved_id = client.users.translate_usernames_to_ids(i)
resolved_id = twitch_client.get_users(logins=[i])
if not resolved_id['data']:
log.error(
colored(
"Аккаунт " + i + " не найден",
'red',
)
)
break
# Создаем путь до диры со стримером, если папка не существует
if not (os.path.exists(path)):
os.makedirs(path)
log.info("Создана директория " + i)
# Достаем ID стримера из инфо
user_id = resolved_id['data'][0]['id']
user_stream = twitch_client.get_streams(user_id=user_id)
# Если стрим идет, то идем дальше
if user_stream['data']:
# Если стрим идет и лок файла нет, то записываем и ставим лок
if (user_stream['data'][0]['type'] == 'live') and not (os.path.exists(config_python.path+"/"+i+"/pid")):
log.info(i + " стримит")
startRecord(i)
open(path+"/pid", 'w').close
else:
log.info(
colored(
"Идет запись " + i,
'red',
attrs=['bold']
)
)
else:
# Если стрим не идет, то пишем об этом и убираем его из залоченных
log.info(i + " Не стримит")
# Если есть лок, то удаляем
if os.path.exists(path + "/pid"):
os.remove(path+"/pid")
def removeOldStreams():
# https://clck.ru/WHh32
records_path = config_python.path
# По каждой папке со стримерами
for i in streamers:
try:
os.chdir(records_path+"/"+i)
# Если файлов в папке со стримами больше чем указано в конфиге
if len(os.listdir(records_path+"/"+i)) > config_python.max_files:
# Получаем список файлов
# и смотрим, превышает ли кол-во mp4 файлов заданное в конфиге
# Если превышает - удаляем старейший
oldest = min(os.listdir(records_path+"/"+i),
key=os.path.getctime)
os.unlink(oldest)
log.warning("Удален файл: " + oldest)
except Exception as e:
log.error(e)
def get_console_handler(): def get_console_handler():
@@ -159,30 +30,204 @@ def get_file_handler():
return file_handler 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): def get_logger(logger_name):
"""
Инициализация лога
"""
logger = logging.getLogger(logger_name) logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
logger.addHandler(get_console_handler())
# Console logging
console = get_console_handler()
console.setFormatter(CustomFormatter())
logger.addHandler(console)
logger.addHandler(get_file_handler()) logger.addHandler(get_file_handler())
logger.propagate = False logger.propagate = False
return logger 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": "asdf,qqqqq"
}
with open('cfg_file.ini', 'w') as cfg_file:
config.write(cfg_file)
return config
def which(command) -> bool:
# Пиздец, почему нет нормального аналога which из bash???
"""
Мой аналог which из bash'а, который отдает true или false
при наличии или отсутствии утилиты
"""
for dirs in os.get_exec_path():
if command in os.listdir(dirs):
# Если что-нибудь нашли, то True
return True
# Если ничего не нашли во всех дирах, то выходим с False
return False
def check_installed_tools() -> bool:
"""
Проверяет, установлены ли необходимые утилиты
"""
tools = ('youtube-dl', 'ffmpeg')
for tool in tools:
if not which(tool):
log.critical("{} не установлен".format(tool))
return False
return True
def start_recording(streamer):
"""
Функция, которая запускает в отдельном потоке запись стрима - recorder(i)
"""
th = Thread(target=recorder, args=(streamer,))
th.start()
def recorder(streamer):
"""
Функция, которая запускает youtube-dl, фактически записывает стрим
"""
streamer_path = os.path.join(config['app']['path'], streamer)
log.info("Записываем стрим {}\n".format(streamer))
# cmdline для запуска youtube-dl
cmdline = ["youtube-dl", "-q", "-o",
streamer_path + "/%(upload_date)s_%(title)s__%(timestamp)s_%(id)s.%(ext)s",
"https://twitch.tv/{}".format(streamer)]
subprocess.call(cmdline)
log.info("Запись стрима {} закончена\n".format(streamer))
if os.path.exists(os.path.join(streamer_path, "pid")):
os.remove(os.path.join(streamer_path, "pid"))
log.info("lock файл удален")
def check_stream():
# FIXME: Распилить на более мелкие функции
"""
1. Проверка на наличие стрима
1.1 Если нет - удалить lock файл, если он есть
1.2 Если есть - создать lock файл, запустить записывалку
"""
for streamer in config['twitch']['streamers'].split(','):
# Путь до диры со стримами
streamer_path = os.path.join(config['app']['path'], streamer)
# Получаем инфо о стримере, если не получается, выходим с ошибкой
# resolved_id = client.users.translate_usernames_to_ids(i)
resolved_id = twitch_client.get_users(logins=[streamer])
if not resolved_id['data']:
log.error(
"Аккаунт {} не найден".format(streamer)
)
continue
# Создаем путь до диры со стримером, если папка не существует
if not (os.path.exists(streamer_path)):
os.makedirs(streamer_path)
log.info("Создана директория {}".format(streamer_path))
# Достаем ID стримера из инфо
user_id = resolved_id['data'][0]['id']
user_stream = twitch_client.get_streams(user_id=user_id)
# Если стрим идет, то идем дальше
if user_stream['data']:
# Если стрим идет и лок файла нет, то записываем и ставим лок
if (user_stream['data'][0]['type'] == 'live') and not (
os.path.exists(os.path.join(streamer_path, "pid"))):
log.info("{} стримит".format(streamer))
start_recording(streamer)
os.mknod(os.path.join(streamer_path, "pid"))
else:
log.info(
"Идет запись {}".format(streamer)
)
else:
# Если стрим не идет, то пишем об этом и убираем его из залоченных
log.info("{} не стримит".format(streamer))
# Если есть лок, то удаляем
if os.path.exists(os.path.join(streamer_path, "pid")):
os.remove(os.path.join(streamer_path, "pid"))
def remove_old_streams():
# https://clck.ru/WHh32
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(): if not check_installed_tools():
exit() exit()
# Set config
config = set_config()
# Log config # Log config
log = get_logger("main") log = get_logger("main")
log.info("Запущен") log.info("Запущен")
# Проверять стримы раз в check_period # Проверять стримы раз в check_period
schedule.every(config_python.check_period).seconds.do(checkAlive) schedule.every(int(config['app']['check_period'])).seconds.do(check_stream)
# Каждый час удалять старые стримы # Каждый час удалять старые стримы
schedule.every(1).hours.do(removeOldStreams) schedule.every(1).hours.do(remove_old_streams)
twitch_client = Twitch(app_id, app_secret) twitch_client = Twitch(config['twitch']['app_id'], config['twitch']['app_secret'])
while True: while True:
schedule.run_pending() schedule.run_pending()
time.sleep(1) time.sleep(1)