@@ -1,150 +1,21 @@
#!/usr/bin/python3
# TODO: Перезапускать скрипт при обнаружении новой версии
# TODO: Сделать нормальную конфигурацию
import os
import sys
from threading import Thread
from types import resolve_ba ses
import config_python
import configpar ser
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 . stre amer s
app_id = config_python . appid
app_secret = config_python . appsecret
log_format = logging . Formatter ( ' %(asctime)s %(levelname)s : %(message)s ' )
log_format = logging . Formatter ( ' %(asctime)s %(leveln ame) 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 )
cfg_file = ' config.ini '
def get_console_handler ( ) :
@@ -159,30 +30,217 @@ def get_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 ) :
"""
Инициализация лога
"""
logger = logging . getLogger ( logger_name )
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 . propagate = False
return logger
i f __name__ == " __main__ " :
# Проверить, установлены ли нужные утилиты
if not checkTools ( ) :
exit ( )
de f 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
при наличии или отсутствии утилиты
"""
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 recorder ( streamer ) :
"""
Функция, которая запускает youtube-dl, фактически записывает стрим
"""
streamer_path = os . path . join ( config [ ' app ' ] [ ' path ' ] , streamer )
log . info ( " Записываем стрим {} " . 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 ( " Запись стрима {} закончена " . format ( streamer ) )
if os . path . exists ( os . path . join ( streamer_path , " pid " ) ) :
os . remove ( os . path . join ( streamer_path , " pid " ) )
log . info ( " lock файл удален " )
def get_streamer_id ( streamer ) :
""" Получаем id стримера, при неудаче отдаем None """
resolved_id = twitch_client . get_users ( logins = [ streamer ] )
if resolved_id [ ' data ' ] :
return resolved_id [ ' data ' ] [ 0 ] [ ' id ' ]
else :
log . error (
" Аккаунт {} не найден " . format ( streamer )
)
return None
def record_streamer ( user_stream , streamer ) :
""" Проверяем, идет ли стрим. Если идет - записываем. Если не идет - удаляем pid файл """
streamer_path = os . path . join ( config [ ' app ' ] [ ' path ' ] , streamer )
if user_stream [ ' data ' ] :
# Создаем путь до диры с о стримером, если папка не существует
if not ( os . path . exists ( streamer_path ) ) :
os . makedirs ( streamer_path )
log . info ( " Создана директория {} " . format ( streamer_path ) )
# Если стрим идет и лок файла нет, то записываем и ставим лок
if ( user_stream [ ' data ' ] [ 0 ] [ ' type ' ] == ' live ' ) and not (
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 :
log . info (
" Идет запись {} " . format ( streamer )
)
else :
# Если стрим не идет, то пишем о б этом и убираем е г о из залоченных
log . debug ( " {} не стримит " . format ( streamer ) )
# Если есть лок, то удаляем
if os . path . exists ( os . path . join ( streamer_path , " pid " ) ) :
os . remove ( os . path . join ( streamer_path , " pid " ) )
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
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__ " :
# Log config
log = get_logger ( " main " )
log . info ( " Запущен " )
# Проверить, установлены ли нужные утилиты
if not check_installed_tools ( ) :
exit ( )
# Set config
config = set_config ( )
# Проверять стримы раз в check_period
schedule . every ( config_python . check_period ) . seconds . do ( checkAlive )
# Каждый час удалять старые стримы
schedule . every ( 1 ) . hour s. do ( removeOldStreams )
schedule . every ( int ( config [ ' app ' ] [ ' check_period ' ] ) ) . second s. do ( streamers_loop )
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 ' ] )
log . info ( " Запущен " )
while True :
schedule . run_pending ( )
time . sleep ( 1 )