Added skeleton for chat and video.

This commit is contained in:
Vitalii Lebedynskyi
2022-08-08 14:24:06 +03:00
parent a84e8abc14
commit e00fb5bd70
5 changed files with 85 additions and 29 deletions

View File

@@ -1,3 +0,0 @@
class ChatAnalyser:
def __init__(self, streamer_name, ):
pass

View File

@@ -1,23 +1,41 @@
import threading
import requests import requests
import logging
TOKEN_URL = "https://id.twitch.tv/oauth2/token?client_id={0}&client_secret={1}&grant_type=client_credentials" TOKEN_URL = "https://id.twitch.tv/oauth2/token?client_id={0}&client_secret={1}&grant_type=client_credentials"
logger = logging.getLogger(__name__)
def synchronized(func):
func.__lock__ = threading.Lock()
def synced_func(*args, **kws):
with func.__lock__:
return func(*args, **kws)
return synced_func
class TwitchAuthenticator: class TwitchAuthenticator:
cached_token = None cached_token = None
def __init__(self, client_id, client_secret): def __init__(self, client_id, client_secret, username):
self.username = username
self.client_id = client_id self.client_id = client_id
self.client_secret = client_secret self.client_secret = client_secret
self.token_url = TOKEN_URL.format(self.client_id, self.client_secret) self.token_url = TOKEN_URL.format(self.client_id, self.client_secret)
@synchronized
def get_token(self): def get_token(self):
if self.cached_token is None: if self.cached_token is None:
self._fetch_token() self._fetch_token()
return self.cached_token return self.cached_token
@synchronized
def refresh_token(self): def refresh_token(self):
# TODO what if both will call refresh ?
self._fetch_token() self._fetch_token()
return self.cached_token return self.cached_token
@@ -26,3 +44,5 @@ class TwitchAuthenticator:
token_response.raise_for_status() token_response.raise_for_status()
token = token_response.json() token = token_response.json()
self.cached_token = token["access_token"] self.cached_token = token["access_token"]
logger.info("Fetched new token %s", self.cached_token)

22
clipper/chat.py Normal file
View File

@@ -0,0 +1,22 @@
import twitch
import logging
logger = logging.getLogger(__name__)
class TwitchChatRecorder:
def __init__(self, authenticator, streamer_name, on_finish=None):
self.on_finish = on_finish
self.streamer_name = streamer_name
self.authenticator = authenticator
def run(self):
chat = twitch.Chat(self.streamer_name, self.authenticator.username, self.authenticator.get_token())
logger.info("Subscribing to chat for %s as %s", self.streamer_name, self.authenticator.username)
chat.subscribe(on_next=self.on_message, on_error=self.on_error)
def on_error(self, error):
logger.error(error)
def on_message(self, msg):
logger.info("New message %s", msg)

View File

@@ -11,7 +11,7 @@ HELIX_STREAM_URL = "https://api.twitch.tv/helix/streams?user_login={0}"
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class TwitchStreamerStatus(enum.Enum): class TwitchStreamStatus(enum.Enum):
ONLINE = 0 ONLINE = 0
OFFLINE = 1 OFFLINE = 1
NOT_FOUND = 2 NOT_FOUND = 2
@@ -19,16 +19,16 @@ class TwitchStreamerStatus(enum.Enum):
ERROR = 4 ERROR = 4
class TwitchRecorder: class TwitchVideoRecorder:
access_token = None access_token = None
def __init__(self, authenticator, streamer_name, output_path, quality="480p", on_download=None): def __init__(self, authenticator, streamer_name, output_path, quality="480p", on_finish=None):
# global configuration # global configuration
self.disable_ffmpeg = False self.disable_ffmpeg = False
self.refresh_timeout = 15 self.refresh_timeout = 15
self.output_path = output_path self.output_path = output_path
self.stream_uid = None self.stream_uid = None
self.on_download = on_download self.on_finish = on_finish
# twitch configuration # twitch configuration
self.streamer_name = streamer_name self.streamer_name = streamer_name
@@ -56,38 +56,40 @@ class TwitchRecorder:
self.streamer_name, self.refresh_timeout, self.quality) self.streamer_name, self.refresh_timeout, self.quality)
while True: while True:
status, info = self.check_user() status, info = self.check_user()
if status == TwitchStreamerStatus.NOT_FOUND: if status == TwitchStreamStatus.NOT_FOUND:
logger.error("streamer_name not found, invalid streamer_name or typo") logger.error("streamer_name not found, invalid streamer_name or typo")
time.sleep(self.refresh_timeout) time.sleep(self.refresh_timeout)
elif status == TwitchStreamerStatus.ERROR: elif status == TwitchStreamStatus.ERROR:
logger.error("%s unexpected error. will try again in 5 minutes", logger.error("%s unexpected error. will try again in 5 minutes",
datetime.datetime.now().strftime("%Hh%Mm%Ss")) datetime.datetime.now().strftime("%Hh%Mm%Ss"))
time.sleep(300) time.sleep(300)
elif status == TwitchStreamerStatus.OFFLINE: elif status == TwitchStreamStatus.OFFLINE:
logger.info("%s currently offline, checking again in %s seconds", self.streamer_name, logger.info("%s currently offline, checking again in %s seconds", self.streamer_name,
self.refresh_timeout) self.refresh_timeout)
time.sleep(self.refresh_timeout) time.sleep(self.refresh_timeout)
elif status == TwitchStreamerStatus.UNAUTHORIZED: elif status == TwitchStreamStatus.UNAUTHORIZED:
logger.info("unauthorized, will attempt to log back in immediately") logger.info("unauthorized, will attempt to log back in immediately")
self.access_token = self.authenticator.refresh_token() self.access_token = self.authenticator.refresh_token()
elif status == TwitchStreamerStatus.ONLINE: elif status == TwitchStreamStatus.ONLINE:
logger.info("%s online, stream recording in session", self.streamer_name) logger.info("%s online, stream recording in session", self.streamer_name)
channels = info["data"] channels = info["data"]
channel = next(iter(channels), None) channel = next(iter(channels), None)
recorded_filename = self.download_stream(channel, recording_path) recorded_filename = self.record_stream(channel, recording_path)
logger.info("recording stream is done") logger.info("recording stream is done")
if self.on_download is not None: if self.on_finish is not None:
self.on_download(recorded_filename) self.on_finish(channel, recorded_filename)
time.sleep(self.refresh_timeout) time.sleep(self.refresh_timeout)
# TODO use twitch library instead of pure requests
def check_user(self): def check_user(self):
info = None info = None
status = TwitchStreamerStatus.ERROR status = TwitchStreamStatus.ERROR
try: try:
headers = {"Client-ID": self.authenticator.client_id, headers = {"Client-ID": self.authenticator.client_id,
"Authorization": "Bearer {}".format(self.authenticator.get_token())} "Authorization": "Bearer {}".format(self.authenticator.get_token())}
@@ -95,18 +97,18 @@ class TwitchRecorder:
r.raise_for_status() r.raise_for_status()
info = r.json() info = r.json()
if info is None or not info["data"]: if info is None or not info["data"]:
status = TwitchStreamerStatus.OFFLINE status = TwitchStreamStatus.OFFLINE
else: else:
status = TwitchStreamerStatus.ONLINE status = TwitchStreamStatus.ONLINE
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
if e.response: if e.response:
if e.response.status_code == 401: if e.response.status_code == 401:
status = TwitchStreamerStatus.UNAUTHORIZED status = TwitchStreamStatus.UNAUTHORIZED
if e.response.status_code == 404: if e.response.status_code == 404:
status = TwitchStreamerStatus.NOT_FOUND status = TwitchStreamStatus.NOT_FOUND
return status, info return status, info
def download_stream(self, channel, recording_path): def record_stream(self, channel, recording_path):
filename = self.streamer_name + " - " + datetime.datetime.now() \ filename = self.streamer_name + " - " + datetime.datetime.now() \
.strftime("%Y-%m-%d %Hh%Mm%Ss") + " - " + channel.get("title") + ".mp4" .strftime("%Y-%m-%d %Hh%Mm%Ss") + " - " + channel.get("title") + ".mp4"

29
main.py
View File

@@ -2,23 +2,30 @@ import argparse
import os import os
import sys import sys
import logging import logging
import threading
from clipper.auth import TwitchAuthenticator from clipper.auth import TwitchAuthenticator
from clipper.recorder import TwitchRecorder from clipper.chat import TwitchChatRecorder
from clipper.video import TwitchVideoRecorder
def parse_arguments(): def parse_arguments():
parser = argparse.ArgumentParser(description='Twitch highlighter') parser = argparse.ArgumentParser(description='Twitch highlighter')
parser.add_argument('--client', "-c", help='Twitch client id', required=True, dest="tw_client") parser.add_argument('--client', "-c", help='Twitch client id', required=True, dest="tw_client")
parser.add_argument('--secret', "-s", help='Twitch secret id', required=True, dest="tw_secret") parser.add_argument('--secret', "-s", help='Twitch secret id', required=True, dest="tw_secret")
parser.add_argument('--user', "-u", help='Twitch streamer username', required=True, dest="tw_streamer") parser.add_argument('--user', "-u", help='Twitch username id', required=True, dest="tw_username")
parser.add_argument('--streamer', "-t", help='Twitch streamer username', required=True, dest="tw_streamer")
parser.add_argument('--quality', "-q", help='Video downloading quality', dest="tw_quality", default="360p") parser.add_argument('--quality', "-q", help='Video downloading quality', dest="tw_quality", default="360p")
parser.add_argument('--output_path', "-o", help='Video download folder', dest="output_path", default=os.getcwd()) parser.add_argument('--output_path', "-o", help='Video download folder', dest="output_path", default=os.getcwd())
return parser.parse_args() return parser.parse_args()
def on_downloaded(): def on_video_recorded(streamer, filename):
pass
def on_chat_recorded(streamer, filename):
pass pass
@@ -28,11 +35,19 @@ if __name__ == "__main__":
args = parse_arguments() args = parse_arguments()
authenticator = TwitchAuthenticator(args.tw_client, args.tw_secret) authenticator = TwitchAuthenticator(args.tw_client, args.tw_secret, args.tw_username)
rec = TwitchRecorder(authenticator, args.tw_streamer, args.output_path,
args.tw_quality, on_download=on_downloaded)
rec.run() video_recorder = TwitchVideoRecorder(authenticator, args.tw_streamer, args.output_path,
args.tw_quality, on_finish=on_video_recorded)
chat_recorder = TwitchChatRecorder(authenticator, args.tw_streamer, on_finish=on_chat_recorded)
# video_thread = threading.Thread(target=video_recorder.run)
# video_thread.start()
chat_thread = threading.Thread(target=chat_recorder.run)
chat_thread.start()
chat_thread.join()
# Twitch downloader # Twitch downloader
# def main(argv): # def main(argv):