diff --git a/.gitignore b/.gitignore index 67b05d3..c25b3a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ list.sh config_python.py config_list.sh -__pycache__ \ No newline at end of file +__pycache__ +.vscode +.idea +output.log \ No newline at end of file diff --git a/README.md b/README.md index ee79f51..aae4b98 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,30 @@ # Что это? -Скрипт который проверяет и начинает запись live стримов +Скрипт, который записывает стримы с твича, удаляет старые, ведет красивый лог с цветами (Alpha) # Как юзать? -* Поставить youtube-dl, python-twitch-client (TODO: создать requirements.txt для автоматической установки) +* Поставить youtube-dl, ffmpeg и другие пакеты из requirements.txt -`pip3 install python-twitch-client youtube-dl` +`apt install youtube-dl ffmpeg -y` -* Переименовать файл conf_python.py.template в conf_python.py и добавить свой ключ (Можно получить на https://dev.twitch.tv/console) +`pip install -r requirements.txt` -* Переименовать файл list.sh.template в list.sh, добавить стримеров в параметре list (через пробел), изменить путь для сохранения стримов в path +* Создать файл conf_python.py и добавить свой ключ (Можно получить на https://dev.twitch.tv/console), а также переменные из config_python.py.template: -* Добавить cron.sh в crontab, ну и офк убедиться что cron.service запущен (systemd timer не подойдет ибо он убивает child процессы после завершения работы родителя) +``` +twitchid = "ID" # ID ключа +streamers = ("jesusavgn", "252mart", "vi0xxx") # список стримеров в таком формате +path = "/путь/до/диры/со/стримами" # путь до директории, куда писать стримы +check_period = 5 # Частота проверки стримеров (в секундах) +max_files = 3 # Сколько хранить стримов +``` -`*/5 * * * * /opt/twitch-downloader/cron.sh` +* Запустить скрипт в screen'е или создать для него systemd.service файл (или init.d, в зависимости от системы инициализации) +# Updates + +## 2022-11-13 + +В связи с переходом на новую библиотеку необходимо указывать 2 переменные авторизации - appid и appsecret. +twitchid это appid, а секретный ключ можно получить или перегенерировать в https://dev.twitch.tv/console \ No newline at end of file diff --git a/check.sh b/check.sh deleted file mode 100755 index b81a624..0000000 --- a/check.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash -########## -# check if stream is live and start recording using youtube-dl -########## - -full_path=$(dirname "$(realpath $0)") - -source $full_path/config_list.sh - - - -#check if not running, kill if running and stream is finished (broken record) -# [ -f $storage_path/$1/pid ] && $full_path/lifeChk.py $1 && exit 0 || kill -9 $(cat $storage_path/$1/pid) - -#if pid exists and stream is live, than exit and do not start recording -[ -f $storage_path/$1/pid ] && $full_path/lifeChk.py $1 $twitchid && exit 0 - -#exit if no stream and remove lock -$full_path/lifeChk.py $1 $twitchid || rm $storage_path/$1/pid ; exit 0 - -echo $$ > $storage_path/$1/pid - -#set pid and start recording -/home/losted/.local/bin/youtube-dl -v -o $storage_path/$1/"%(upload_date)s_%(title)s__%(timestamp)s_%(id)s.%(ext)s" twitch.tv/$1 >> $storage_path/$1/youtube-dl.log 2>&1 - -# remove pid -rm $storage_path/$1/pid diff --git a/config_list.sh.template b/config_list.sh.template deleted file mode 100644 index 362e959..0000000 --- a/config_list.sh.template +++ /dev/null @@ -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" diff --git a/config_python.py.template b/config_python.py.template new file mode 100644 index 0000000..dfa3865 --- /dev/null +++ b/config_python.py.template @@ -0,0 +1,5 @@ +appid="" +appsecret="" +streamers = ("jesusavgn", "252mart", "vi0xxx") +path="/home/losted/test" +period = 5 \ No newline at end of file diff --git a/cron.sh b/cron.sh deleted file mode 100755 index b333ac5..0000000 --- a/cron.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -########## -# (actually main) script which cron (systemd.timer) starts by time -########## -full_path=$(dirname "$(realpath $0)") -source $full_path/config_list.sh - -for i in $list; do - echo "$i is live?..." - - #check folder - [ ! -d $storage_path/$i ] && mkdir -p $storage_path/$i && echo "Created dir $storage_path/$i" - - #detached check & start - nohup bash $full_path/check.sh $i &>> $storage_path/$1/youtube-dl.log & - sleep 2 - -done - -#Show status -echo -for i in $list; do - [[ -f $storage_path/$i/pid ]] && echo $i "is recording!" || echo $i "is not recording" -done -echo -echo "Removing old files (older than $ctime_remove days):" -find $storage_path/ -ctime +$ctime_remove -name "*mp4*" -find $storage_path/ -ctime +$ctime_remove -name "*mp4*" -exec rm {} \; -echo "Done" \ No newline at end of file diff --git a/daemon.py b/daemon.py new file mode 100644 index 0000000..f405ec0 --- /dev/null +++ b/daemon.py @@ -0,0 +1,188 @@ +#!/usr/bin/python3 + +# TODO: Перезапускать скрипт при обнаружении новой версии +# TODO: Сделать нормальную конфигурацию + +import os +import sys +from threading import Thread +from types import resolve_bases +import config_python +import schedule +from twitchAPI.twitch import Twitch +import subprocess +import time +import logging +from logging.handlers import TimedRotatingFileHandler +from termcolor import colored + +streamers = config_python.streamers +app_id = config_python.appid +app_secret = config_python.appsecret + +log_format = logging.Formatter('%(asctime)s %(levelname)s:%(message)s') +log_file = 'output.log' + + +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(): + 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 + + +def get_logger(logger_name): + logger = logging.getLogger(logger_name) + logger.setLevel(logging.DEBUG) + logger.addHandler(get_console_handler()) + logger.addHandler(get_file_handler()) + logger.propagate = False + return logger + + +if __name__ == "__main__": + # Проверить, установлены ли нужные утилиты + if not checkTools(): + exit() + + # Log config + log = get_logger("main") + log.info("Запущен") + + # Проверять стримы раз в check_period + schedule.every(config_python.check_period).seconds.do(checkAlive) + # Каждый час удалять старые стримы + schedule.every(1).hours.do(removeOldStreams) + + twitch_client = Twitch(app_id, app_secret) + while True: + schedule.run_pending() + time.sleep(1) diff --git a/lifeChk.py b/lifeChk.py deleted file mode 100755 index aaf7e16..0000000 --- a/lifeChk.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/python3 -################# -# return 0 if streamer is live (continue execution while in bash), 1 if not -################# -import sys -if (not sys.argv[1]) or (not sys.argv[2]): - sys.exit(2) - -from twitch import TwitchClient - -client = TwitchClient(client_id=sys.argv[2]) #client init -user_id=client.users.translate_usernames_to_ids(sys.argv[1])[0].id #get id -#get live by id (if var not empty) - -if client.streams.get_stream_by_user(user_id): - print(user_id) - print(client.streams.get_stream_by_user(user_id).stream_type) - if client.streams.get_stream_by_user(user_id).stream_type == 'live': - sys.exit(0) - -sys.exit(1) diff --git a/requirements.txt b/requirements.txt index a757172..4fece1b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ youtube-dl==2021.4.17 -python-twitch-client==0.7.1 \ No newline at end of file +twitchAPI==2.5.7.1 +schedule +termcolor