This commit is contained in:
Piero 2022-12-11 10:09:52 -05:00
commit 4e861b6c43
5 changed files with 323 additions and 272 deletions

View file

@ -16,10 +16,11 @@ Python script to check, download live stream, VOD, chat and upload them to any c
```.env ```.env
CLIENT-ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx CLIENT-ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
CLIENT-SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx CLIENT-SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
OAUTH-PRIVATE-TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx OAUTH-PRIVATE-TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx # optional to record without ADS or download sub-only VODS
``` ```
8. if you want to enable/disable more available options, edit `twitch-archive.py` 8. if you want to enable/disable more available options, edit `twitch-archive.py`
9. run `Python twitch-archive.py` or for multiple streamers `Python twitch-archive.py -u streamer` 9. run `Python twitch-archive.py` or for multiple streamers `Python twitch-archive.py -u streamer`
<!---
## Features ## Features
- Auto records the live stream | [Streamlink](https://streamlink.github.io/) - Auto records the live stream | [Streamlink](https://streamlink.github.io/)
- Downloads the VOD after stream ended | [Streamlink](https://streamlink.github.io/) - Downloads the VOD after stream ended | [Streamlink](https://streamlink.github.io/)
@ -27,3 +28,4 @@ OAUTH-PRIVATE-TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- Downloads the metadata of the VOD | [Twitch api](https://dev.twitch.tv/docs/api/reference#get-videos) - Downloads the metadata of the VOD | [Twitch api](https://dev.twitch.tv/docs/api/reference#get-videos)
- Uploads them to the Cloud | [rclone](https://rclone.org/) - Uploads them to the Cloud | [rclone](https://rclone.org/)
- Notifies you through Gmail of the progress | [smtplib](https://docs.python.org/3/library/smtplib.html) - Notifies you through Gmail of the progress | [smtplib](https://docs.python.org/3/library/smtplib.html)
-->

View file

@ -5,4 +5,4 @@ if "%root_path:~-1%" == "\" set "root_path_1=%root_path:~0,-1%"
for %%f in ("%root_path_1%") do set "root_path_name=%%~nxf" for %%f in ("%root_path_1%") do set "root_path_name=%%~nxf"
FOR /F "eol=# tokens=*" %%i in (%~dp0\..\.env) do SET %%i FOR /F "eol=# tokens=*" %%i in (%~dp0\..\.env) do SET %%i
CD %~dp0 CD %~dp0
rclone.exe copy %root_path%/%user% %remote%/%root_path_name%/%user% --progress rclone.exe copy %root_path%/%user% %remote%/%root_path_name%/%user% --progress --include-from %~dp0/temp/upload.txt

View file

@ -1,2 +1,2 @@
#!/bin/sh #!/bin/sh
rclone copy $1/$2 gd:VODS/$(basename $1)/$2 --progress rclone copy $1/$2 gd:VODS/$(basename $1)/$2 --progress --include-from $(dirname "$0")/temp/upload.txt

View file

@ -1,6 +1,7 @@
import requests, os, time, json, sys, subprocess, getopt, pathlib import requests, os, time, json, sys, subprocess, getopt, pathlib, locale
from colorama import Fore, Style from colorama import Fore, Style
from datetime import datetime from datetime import datetime, timedelta
locale.setlocale(locale.LC_TIME, "es_ES")
from pytz import timezone from pytz import timezone
from dotenv import load_dotenv, find_dotenv from dotenv import load_dotenv, find_dotenv
@ -13,10 +14,10 @@ class TwitchArchive:
# global configuration # global configuration
self.root_path = r"archive" # Path where this script saves everything (livestream,VODs,chat,metadata) self.root_path = r"archive" # Path where this script saves everything (livestream,VODs,chat,metadata)
self.refresh = 60 # Time between checking (5.0 is recommended), avoid less than 1.0 self.refresh = 60 # Time between checking (5.0 is recommended), avoid less than 1.0
self.downloadVOD = 0 # 0 - disable VOD downloading after stream finished, 1 - enable VOD downloading after stream finished (this option downloads the latest public vod) self.downloadVOD = 1 # 0 - disable VOD downloading after stream finished, 1 - enable VOD downloading after stream finished (this option downloads the latest public vod)
self.downloadCHAT = 1 # 0 - disable chat downloading and rendering, 1 - enable chat downloading and rendering self.downloadCHAT = 1 # 0 - disable chat downloading and rendering, 1 - enable chat downloading and rendering
self.uploadCloud = 1 # 0 - disable upload to remote cloud, 1 - enable upload to remote cloud self.uploadCloud = 1 # 0 - disable upload to remote cloud, 1 - enable upload to remote cloud
self.deleteFiles = 1 # 0 - disable the deleting of files from current seccion after being uploaded to the cloud, 1 - enable the deleting files of files from current seccion after being uploaded to the cloud (BE CAREFUL WITH THIS OPTION) self.deleteFiles = 0 # 0 - disable the deleting of files from current seccion after being uploaded to the cloud, 1 - enable the deleting files of files from current seccion after being uploaded to the cloud (BE CAREFUL WITH THIS OPTION)
self.hls_segmentsVOD = 10 # 1-10 for downloading vod, it's possible to use multiple threads to potentially increase the throughput self.hls_segmentsVOD = 10 # 1-10 for downloading vod, it's possible to use multiple threads to potentially increase the throughput
def run(self): def run(self):
@ -38,18 +39,18 @@ class TwitchArchive:
self.oauth_token = self.get_oauth_token() self.oauth_token = self.get_oauth_token()
self.channel_id = self.get_channel_id() self.channel_id = self.get_channel_id()
self.temp_path = str(pathlib.Path(os.path.join(self.root_path,self.username,"vod", "temp")).absolute()) self.temp_path = str(pathlib.Path(os.path.join("VODS",self.root_path,self.username,"vod", "temp")).absolute())
self.vod_path = str(pathlib.Path(os.path.join(self.root_path, self.username, "vod")).absolute()) self.vod_path = str(pathlib.Path(os.path.join("VODS",self.root_path, self.username, "vod")).absolute())
self.json_path = str(pathlib.Path(os.path.join(self.root_path, self.username, "chat", "json")).absolute()) self.json_path = str(pathlib.Path(os.path.join("VODS",self.root_path, self.username, "chat", "json")).absolute())
self.chat_path = str(pathlib.Path(os.path.join(self.root_path, self.username, "chat")).absolute()) self.chat_path = str(pathlib.Path(os.path.join("VODS",self.root_path, self.username, "chat")).absolute())
if(os.path.isdir(self.temp_path) is False): os.makedirs(self.temp_path) if(os.path.isdir(self.temp_path) is False): os.makedirs(self.temp_path)
if(os.path.isdir(self.vod_path) is False): os.makedirs(self.vod_path) if(os.path.isdir(self.vod_path) is False): os.makedirs(self.vod_path)
if(os.path.isdir(self.json_path) is False): os.makedirs(self.json_path) if(os.path.isdir(self.json_path) is False): os.makedirs(self.json_path)
if(os.path.isdir(self.chat_path) is False): os.makedirs(self.chat_path) if(os.path.isdir(self.chat_path) is False): os.makedirs(self.chat_path)
if not os.path.exists(os.path.join(self.root_path, ".log")): if not os.path.exists(os.path.join('VODS',self.root_path, ".log")):
with open(os.path.join(self.root_path, ".log"), 'w'): pass with open(os.path.join('VODS',self.root_path, ".log"), 'w'): pass
print(f"Checking for {Fore.GREEN}{self.username}{Style.RESET_ALL} every {Fore.GREEN}{self.refresh}{Style.RESET_ALL} seconds to download VOD/CHAT") print(f"Checking for {Fore.GREEN}{self.username}{Style.RESET_ALL} every {Fore.GREEN}{self.refresh}{Style.RESET_ALL} seconds to download VOD/CHAT")
self.loopcheck() self.loopcheck()
@ -63,123 +64,161 @@ class TwitchArchive:
print('OS no supported') print('OS no supported')
return return
def get_oauth_token(self):
try:
return requests.post(f"https://id.twitch.tv/oauth2/token?client_id={os.getenv('CLIENT-ID')}&client_secret={os.getenv('CLIENT-SECRET')}&grant_type=client_credentials").json()['access_token']
except:
return None
def get_channel_id(self):
try:
r = requests.get(f'https://api.twitch.tv/helix/users?login={self.username}', headers = {"Authorization" : "Bearer " + self.oauth_token, "Client-ID": os.getenv('CLIENT-ID')}, timeout = 15)
r.raise_for_status()
info = r.json()
if info["data"] != []:
return info["data"][0]["id"]
else:
return None
except requests.exceptions.RequestException as e:
print(e)
def check_user(self): def check_user(self):
try: client_id = "kimne78kx3ncx6brgo4mv6wki5h1ko"
url = 'https://api.twitch.tv/helix/streams?user_id=' + self.channel_id query = '''
live = requests.get(url, headers = {"Authorization" : "Bearer " + self.oauth_token, "Client-ID": os.environ.get('CLIENT-ID')}, timeout = 30) query {
stream_data = live.json() user(login: "''' + self.username + '''") {
if len(stream_data['data']) == 1: stream {
self.live_info = stream_data['data'][0] archiveVideo{
return True id
else: }
return False title
except Exception as e: createdAt
print("API request ERROR") }
print(e) }
return False }
'''
url = 'https://gql.twitch.tv/gql'
response = requests.post(url,json={'query': query},headers={"Client-ID": client_id})
return json.loads(response.text)
def get_vod(self):
client_id = "kimne78kx3ncx6brgo4mv6wki5h1ko"
query = '''
query {
user(login: "''' + self.username + '''") {
videos(first: 1) {
edges {
node {
id
scope
title
description
recordedAt
lengthSeconds
animatedPreviewURL
previewThumbnailURL(height: 1280, width: 720)
thumbnailURLs(height: 1280, width: 720)
}
}
}
}
}
'''
url = 'https://gql.twitch.tv/gql'
response = requests.post(url, json={'query': query}, headers={"Client-ID": client_id})
return json.loads(response.text)
def loopcheck(self): def loopcheck(self):
while True: while True:
if self.check_user() is True: is_live = self.check_user()['data']['user']['stream']
live_temp_path = os.path.join(self.temp_path, "live_temp.ts") if is_live is not None:
with open(os.path.join(self.root_path, ".log")) as logs: is_live_ready = self.check_user()['data']['user']['stream']['title']
logs = logs.read() if is_live_ready is not None:
log_id = self.live_info["started_at"] + " - " + self.username + " - " + self.live_info["title"] live_date = datetime.strptime(is_live["createdAt"],'%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone('UTC')).astimezone(tz=None).replace(tzinfo=None)
if log_id in logs: live_temp_path = os.path.join(self.temp_path, "live_temp.ts")
time.sleep(self.refresh)
with open(os.path.join(self.root_path, ".log"), "r+") as logs: with open(os.path.join(self.root_path, ".log")) as logs:
log_id = self.live_info["started_at"] + " - " + self.username + " - " + self.live_info["title"] logs = logs.read()
for line in logs: log_id = is_live["createdAt"] + " - " + self.username + " - " + is_live["title"]
if log_id in line: if log_id in logs:
break time.sleep(self.refresh)
else:
logs.write(self.live_info["started_at"] + " - " + self.username + " - " + self.live_info["title"] + "\n")
subprocess.call(['streamlink', 'twitch.tv/'+ self.username, 'worst', '--hls-segment-threads', '3', '--retry-streams', str(self.refresh), '--twitch-disable-reruns', '-o', live_temp_path]) with open(os.path.join(self.root_path, ".log"), "r+") as logs:
try: log_id = is_live["createdAt"] + " - " + self.username + " - " + is_live["title"]
vodurl = f'https://api.twitch.tv/helix/videos?user_id={str(self.channel_id)}&period=day&type=archive' for line in logs:
vods = requests.get(vodurl, headers = {"Authorization" : "Bearer " + self.oauth_token, "Client-ID": os.getenv('CLIENT-ID')}, timeout = 30) if log_id in line:
vodsinfo = json.loads(vods.text) break
if vodsinfo["data"][0] != []:
vod_date = datetime.strptime(vodsinfo["data"][0]["created_at"],'%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone('UTC')).astimezone(tz=None).replace(tzinfo=None)
vod_raw_filename = datetime.strftime(vod_date,'%Y%m%d_%Hh%Mm%Ss')
if self.live_info["id"] == vodsinfo["data"][0]["stream_id"]:
print('VOD AND CHAT AVAILABLE')
current_vod = vodsinfo["data"][0]
vod_raw_path = os.path.join(self.temp_path, "vod_temp.ts")
vod_proc_path = os.path.join(self.vod_path, vod_raw_filename + ".mp4")
chat_json_path = os.path.join(self.json_path, vod_raw_filename + ".json")
chat_video_path = os.path.join(self.chat_path, vod_raw_filename + ".mp4")
if self.downloadVOD == 1:
print('Downloading VOD: ' + current_vod["title"])
try:
subprocess.call(['streamlink', 'twitch.tv/videos/' + current_vod["id"], self.quality, "--hls-segment-threads", str(self.hls_segmentsVOD), "-o", vod_raw_path])
if(os.path.exists(vod_raw_path) is True):
if self.os == 'windows':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/ffmpeg.exe', '-y', '-i', vod_raw_path, '-analyzeduration', '2147483647', '-probesize', '2147483647', '-c:v', 'copy', '-c:a', 'copy', '-start_at_zero', '-copyts', vod_proc_path], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
elif self.os == 'linux':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/ffmpeg', '-y', '-i', vod_raw_path, '-analyzeduration', '2147483647', '-probesize', '2147483647', '-c:v', 'copy', '-c:a', 'copy', '-start_at_zero', '-copyts', vod_proc_path], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
else:
print("Skip fixing. File not found.")
except Exception as e:
print('Error', 'A ERROR has ocurred and the VOD will not be downloaded.\n')
if self.downloadCHAT == 1:
print('Downloading and rendering CHAT: ' + current_vod["title"])
try:
if self.os == 'windows':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+"/bin/chat.bat", current_vod["id"], chat_json_path, chat_video_path])
elif self.os == 'linux':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+"/bin/chat.sh", current_vod["id"], chat_json_path, chat_video_path])
except Exception as e:
print("A ERROR has ocurred and chat will need to be downloaded and rendered manually\n")
if self.uploadCloud == 1:
if self.os == 'windows':
tree = subprocess.run(['powershell.exe','tree', f'{self.root_path}/{self.username}', '/f'], capture_output=True, text=True).stdout.split("\n",2)[2]
elif self.os == 'linux':
tree = subprocess.check_output(['tree', str(pathlib.Path(self.root_path).resolve())+'/'+self.username]).decode(sys.stdout.encoding)
print('Uploading the following files:\n' + tree)
if self.os == 'windows':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/upload.bat', str(pathlib.Path(self.root_path).resolve()),self.username])
elif self.os == 'linux':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/upload.sh', str(pathlib.Path(self.root_path).resolve()),self.username])
if self.deleteFiles == 1:
print(f'{Fore.RED}DELETING FILES{Style.RESET_ALL}')
if self.downloadVOD == 1:
if(os.path.exists(vod_raw_path) is True):
print(f'{Fore.RED}Deleting ' + vod_raw_path + f'{Style.RESET_ALL}')
os.remove(vod_raw_path)
if(os.path.exists(vod_proc_path) is True):
print(f'{Fore.RED}Deleting ' + vod_proc_path + f'{Style.RESET_ALL}')
os.remove(vod_proc_path)
if self.downloadCHAT == 1:
if(os.path.exists(chat_json_path) is True):
print(f'{Fore.RED}Deleting ' + chat_json_path + f'{Style.RESET_ALL}')
os.remove(chat_json_path)
if(os.path.exists(chat_video_path) is True):
print(f'{Fore.RED}Deleting ' + chat_video_path + f'{Style.RESET_ALL}')
os.remove(chat_video_path)
else: else:
print('THE VOD/CHAT FOR CURRENT LIVESTREAM IS NOT AVAILABLE\nThe current livestream date: ' + self.live_info['started_at'] + '\nThe VOD date: ' + vodsinfo["data"][0]["created_at"]) logs.write(is_live["createdAt"] + " - " + self.username + " - " + is_live["title"] +"\n")
except Exception as e:
print('API request error.')
print(e)
print('CURRENT SECCION HAVE FINISHED GOING BACK TO CHECKING') subprocess.call(['streamlink', 'twitch.tv/'+ self.username, self.quality, '--twitch-api-header', 'Authorization=OAuth ' + os.getenv('OAUTH-PRIVATE-TOKEN'), '--hls-segment-threads', str(self.hls_segments), '--hls-live-restart', '--retry-streams', str(self.refresh), '--twitch-disable-reruns', '-o', live_temp_path])
time.sleep(self.refresh) os.remove(live_temp_path)
current_vod = self.get_vod()['data']['user']['videos']['edges'][0]['node']
vod_date = datetime.strptime(current_vod["recordedAt"],'%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone('UTC')).astimezone(tz=None).replace(tzinfo=None)
vod_raw_filename = datetime.strftime(vod_date,'%Y%m%d_%Hh%Mm%Ss')
live_date_min = live_date - timedelta(minutes=1)
live_date_max = live_date + timedelta(minutes=1)
if live_date_min <= vod_date <= live_date_max:
print('VOD AND CHAT AVAILABLE')
vod_raw_path = os.path.join(self.temp_path, "vod_temp.ts")
vod_proc_path = os.path.join(self.vod_path, vod_raw_filename + ".mp4")
chat_json_path = os.path.join(self.json_path, vod_raw_filename + ".json")
chat_video_path = os.path.join(self.chat_path, vod_raw_filename + ".mp4")
if self.username == 'KalathrasLolweapon':
file_date = datetime.strptime(vod_raw_filename, '%Y%m%d_%Hh%Mm%Ss').date()
week_first = file_date - timedelta(days=file_date.weekday())
week_last = week_first + timedelta(days=6)
vod_year = 'VOD - ' + str(file_date.year)
vod_month = f'{file_date.month:02d} - ' + file_date.strftime("%B").upper()
vod_week = file_date.strftime("%B").capitalize() + ' ' + str(week_first.day) + '-' + str(week_last.day)
chat_year = 'Chat - ' + str(file_date.year)
chat_month = f'{file_date.month:02d} - ' + file_date.strftime("%B")
vod_path = str(pathlib.Path(os.path.join("VODS",vod_year,vod_month,vod_week)).absolute())
chat_path = str(pathlib.Path(os.path.join("Chat",chat_year,chat_month)).absolute())
if(os.path.isdir(vod_path) is False): os.makedirs(vod_path)
if(os.path.isdir(chat_path) is False): os.makedirs(chat_path)
chat_video_path = os.path.join(chat_path, vod_raw_filename + ".mp4")
vod_proc_path = os.path.join(vod_path, vod_raw_filename + ".mp4")
if self.downloadVOD == 1:
print('Downloading VOD: ' + current_vod["title"])
try:
subprocess.call(['streamlink', 'twitch.tv/videos/' + current_vod["id"], self.quality, "--hls-segment-threads", str(self.hls_segmentsVOD), "-o", vod_raw_path])
if(os.path.exists(vod_raw_path) is True):
if self.os == 'windows':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/ffmpeg.exe', '-y', '-i', vod_raw_path, '-analyzeduration', '2147483647', '-probesize', '2147483647', '-c:v', 'copy', '-c:a', 'copy', '-start_at_zero', '-copyts', vod_proc_path], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
elif self.os == 'linux':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/ffmpeg', '-y', '-i', vod_raw_path, '-analyzeduration', '2147483647', '-probesize', '2147483647', '-c:v', 'copy', '-c:a', 'copy', '-start_at_zero', '-copyts', vod_proc_path], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
os.remove(vod_raw_path)
else:
print("Skip fixing. File not found.")
except Exception as e:
print('Error', 'A ERROR has ocurred and the VOD will not be downloaded.\n')
if self.downloadCHAT == 1:
print('Downloading and rendering CHAT: ' + current_vod["title"])
try:
if self.os == 'windows':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+"/bin/chat.bat", current_vod["id"], chat_json_path, chat_video_path])
elif self.os == 'linux':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+"/bin/chat.sh", current_vod["id"], chat_json_path, chat_video_path])
except Exception as e:
print("A ERROR has ocurred and chat will need to be downloaded and rendered manually\n")
if self.uploadCloud == 1:
print('Uploading files:')
if self.os == 'windows':
if self.username == 'KalathrasLolweapon':
subprocess.call(['rclone', 'copy', str(pathlib.Path(__file__).parent.resolve())+'/VODS', 'gd:VODS', '--progress'])
subprocess.call(['rclone', 'copy', str(pathlib.Path(__file__).parent.resolve())+'/Chat', 'gd:Chat', '--progress'])
else:subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/upload.bat', str(pathlib.Path(self.root_path).resolve()),self.username])
elif self.os == 'linux':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/upload.sh', str(pathlib.Path(self.root_path).resolve()),self.username])
if self.deleteFiles == 1:
print(f'{Fore.RED}DELETING FILES{Style.RESET_ALL}')
if self.downloadVOD == 1:
if(os.path.exists(vod_raw_path) is True):
print(f'{Fore.RED}Deleting ' + vod_raw_path + f'{Style.RESET_ALL}')
os.remove(vod_raw_path)
if(os.path.exists(vod_proc_path) is True):
print(f'{Fore.RED}Deleting ' + vod_proc_path + f'{Style.RESET_ALL}')
os.remove(vod_proc_path)
if self.downloadCHAT == 1:
if(os.path.exists(chat_json_path) is True):
print(f'{Fore.RED}Deleting ' + chat_json_path + f'{Style.RESET_ALL}')
os.remove(chat_json_path)
if(os.path.exists(chat_video_path) is True):
print(f'{Fore.RED}Deleting ' + chat_video_path + f'{Style.RESET_ALL}')
os.remove(chat_video_path)
else:
print('THE VOD/CHAT FOR CURRENT LIVESTREAM IS NOT AVAILABLE\nThe current livestream date: ' + is_live["createdAt"] + '\nThe VOD date: ' + current_vod["recordedAt"])
print('CURRENT SECCION HAVE FINISHED GOING BACK TO CHECKING')
time.sleep(self.refresh)
else: time.sleep(self.refresh)
else: time.sleep(self.refresh)
def main(argv): def main(argv):
twitch_archive = TwitchArchive() twitch_archive = TwitchArchive()
help_msg = 'Twitch-Archive\nPython script to download the VOD and/or chat and render it, upload them to any cloud storage.\n -h, --help Display this information\n -u, --username <username> Twitch channel username\n -q, --quality <quality> best/source high/720p medium/480p worst/360p\n -v, --vod <1/0> Download vod\n -c, --chat <1/0> Download chat and render it\n -r, --upload <1/0> Upload to cloud storage\n -d, --delete <1/0> Delete all files after upload (CAREFUL with this arg)\n' help_msg = 'Twitch-Archive\nPython script to download the VOD and/or chat and render it, upload them to any cloud storage.\n -h, --help Display this information\n -u, --username <username> Twitch channel username\n -q, --quality <quality> best/source high/720p medium/480p worst/360p\n -v, --vod <1/0> Download vod\n -c, --chat <1/0> Download chat and render it\n -r, --upload <1/0> Upload to cloud storage\n -d, --delete <1/0> Delete all files after upload (CAREFUL with this arg)\n'

View file

@ -14,12 +14,12 @@ class TwitchArchive:
# global configuration # global configuration
self.root_path = r"archive" # Path where this script saves everything (livestream,VODs,chat,metadata) self.root_path = r"archive" # Path where this script saves everything (livestream,VODs,chat,metadata)
self.refresh = 5.0 # Time between checking (5.0 is recommended), avoid less than 1.0 self.refresh = 5.0 # Time between checking (5.0 is recommended), avoid less than 1.0
self.notifications = 0 # 0 - disable email notification of current seccion, 1 - enable email notification of current seccion self.notifications = 1 # 0 - disable email notification of current seccion, 1 - enable email notification of current seccion
self.downloadMETADATA = 1 # 0 - disable metadata downloading, 1 - enable metadata downloading self.downloadMETADATA = 1 # 0 - disable metadata downloading, 1 - enable metadata downloading
self.downloadVOD = 1 # 0 - disable VOD downloading after stream finished, 1 - enable VOD downloading after stream finished (this option downloads the latest public vod) self.downloadVOD = 1 # 0 - disable VOD downloading after stream finished, 1 - enable VOD downloading after stream finished (this option downloads the latest public vod)
self.downloadCHAT = 1 # 0 - disable chat downloading and rendering, 1 - enable chat downloading and rendering self.downloadCHAT = 1 # 0 - disable chat downloading and rendering, 1 - enable chat downloading and rendering
self.uploadCloud = 0 # 0 - disable upload to remote cloud, 1 - enable upload to remote cloud self.uploadCloud = 1 # 0 - disable upload to remote cloud, 1 - enable upload to remote cloud
self.deleteFiles = 0 # 0 - disable the deleting of files from current seccion after being uploaded to the cloud, 1 - enable the deleting files of files from current seccion after being uploaded to the cloud (BE CAREFUL WITH THIS OPTION) self.deleteFiles = 1 # 0 - disable the deleting of files from current seccion after being uploaded to the cloud, 1 - enable the deleting files of files from current seccion after being uploaded to the cloud (BE CAREFUL WITH THIS OPTION)
self.cleanRaw = 1 # 0 - disable the deleting of raw (.ts) files, 1 - enable the deleteing of raw (.ts) files (if upload enable they will be deleted before) self.cleanRaw = 1 # 0 - disable the deleting of raw (.ts) files, 1 - enable the deleteing of raw (.ts) files (if upload enable they will be deleted before)
self.hls_segments = 3 # 1-10 for live stream, it's possible to use multiple threads to potentially increase the throughput. 2-3 is enough self.hls_segments = 3 # 1-10 for live stream, it's possible to use multiple threads to potentially increase the throughput. 2-3 is enough
self.hls_segmentsVOD = 10 # 1-10 for downloading vod, it's possible to use multiple threads to potentially increase the throughput self.hls_segmentsVOD = 10 # 1-10 for downloading vod, it's possible to use multiple threads to potentially increase the throughput
@ -44,9 +44,6 @@ class TwitchArchive:
else: print(f'{Fore.GREEN}'+'\033[1m'+f'Files will NOT be deleted{Style.RESET_ALL}') else: print(f'{Fore.GREEN}'+'\033[1m'+f'Files will NOT be deleted{Style.RESET_ALL}')
if self.uploadCloud == 0 and self.deleteFiles == 1: print(f'{Fore.RED}'+'\033[1m'+f'FILES WILL BE DELETED AND NO UPLOADED {Style.RESET_ALL}{Fore.GREEN}\n"CTRL + C"{Style.RESET_ALL}{Fore.RED}'+'\033[1m'+f' TO STOP AND CHANGED CONFIGURATION{Style.RESET_ALL}') if self.uploadCloud == 0 and self.deleteFiles == 1: print(f'{Fore.RED}'+'\033[1m'+f'FILES WILL BE DELETED AND NO UPLOADED {Style.RESET_ALL}{Fore.GREEN}\n"CTRL + C"{Style.RESET_ALL}{Fore.RED}'+'\033[1m'+f' TO STOP AND CHANGED CONFIGURATION{Style.RESET_ALL}')
self.oauth_token = self.get_oauth_token()
self.channel_id = self.get_channel_id()
self.raw_path = str(pathlib.Path(os.path.join(self.root_path,self.username,"video", "raw")).absolute()) self.raw_path = str(pathlib.Path(os.path.join(self.root_path,self.username,"video", "raw")).absolute())
self.video_path = str(pathlib.Path(os.path.join(self.root_path, self.username, "video")).absolute()) self.video_path = str(pathlib.Path(os.path.join(self.root_path, self.username, "video")).absolute())
self.chatJSON_path = str(pathlib.Path(os.path.join(self.root_path, self.username, "chat", "json")).absolute()) self.chatJSON_path = str(pathlib.Path(os.path.join(self.root_path, self.username, "chat", "json")).absolute())
@ -74,36 +71,52 @@ class TwitchArchive:
print('OS no supported') print('OS no supported')
return return
def get_oauth_token(self):
try:
return requests.post(f"https://id.twitch.tv/oauth2/token?client_id={os.getenv('CLIENT-ID')}&client_secret={os.getenv('CLIENT-SECRET')}&grant_type=client_credentials").json()['access_token']
except:
return None
def get_channel_id(self):
try:
r = requests.get(f'https://api.twitch.tv/helix/users?login={self.username}', headers = {"Authorization" : "Bearer " + self.oauth_token, "Client-ID": os.getenv('CLIENT-ID')}, timeout = 15)
r.raise_for_status()
info = r.json()
if info["data"] != []:
return info["data"][0]["id"]
else:
return None
except requests.exceptions.RequestException as e:
print(f'\n{e}\n')
def check_user(self): def check_user(self):
try: client_id = "kimne78kx3ncx6brgo4mv6wki5h1ko"
url = 'https://api.twitch.tv/helix/streams?user_id=' + self.channel_id query = '''
live = requests.get(url, headers = {"Authorization" : "Bearer " + self.oauth_token, "Client-ID": os.environ.get('CLIENT-ID')}, timeout = 30) query {
stream_data = live.json() user(login: "''' + self.username + '''") {
if len(stream_data['data']) == 1: stream {
self.live_info = stream_data['data'][0] archiveVideo{
return True id
else: }
return False title
except Exception as e: createdAt
print("ERROR checking user: ", e) }
return False }
}
'''
url = 'https://gql.twitch.tv/gql'
response = requests.post(url,json={'query': query},headers={"Client-ID": client_id})
return json.loads(response.text)
def get_vod(self):
client_id = "kimne78kx3ncx6brgo4mv6wki5h1ko"
query = '''
query {
user(login: "''' + self.username + '''") {
videos(first: 1) {
edges {
node {
id
scope
title
description
recordedAt
lengthSeconds
animatedPreviewURL
previewThumbnailURL(height: 1280, width: 720)
thumbnailURLs(height: 1280, width: 720)
}
}
}
}
}
'''
url = 'https://gql.twitch.tv/gql'
response = requests.post(url, json={'query': query}, headers={"Client-ID": client_id})
return json.loads(response.text)
def sendNotif(self, subject, content): def sendNotif(self, subject, content):
if self.notifications == 1: if self.notifications == 1:
@ -124,127 +137,124 @@ class TwitchArchive:
def loopcheck(self): def loopcheck(self):
while True: while True:
if self.check_user() is True: is_live = self.check_user()['data']['user']['stream']
live_date = datetime.strptime(self.live_info["started_at"],'%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone('UTC')).astimezone(tz=None).replace(tzinfo=None) if is_live is not None:
live_raw_filename = datetime.strftime(live_date,'%Y%m%d_%Hh%Mm%Ss') is_live_ready = self.check_user()['data']['user']['stream']['title']
if is_live_ready is not None:
live_raw_path = os.path.join(self.raw_path, "LIVE_" + live_raw_filename + ".ts") live_date = datetime.strptime(is_live["createdAt"],'%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone('UTC')).astimezone(tz=None).replace(tzinfo=None)
live_proc_path = os.path.join(self.video_path, "LIVE_" + live_raw_filename + ".mp4") live_raw_filename = datetime.strftime(live_date,'%Y%m%d_%Hh%Mm%Ss')
with open(os.path.join(self.root_path, ".log")) as logs:
logs = logs.read()
log_id = self.live_info["started_at"] + " - " + self.live_info["title"]
if log_id in logs:
time.sleep(self.refresh)
with open(os.path.join(self.root_path, ".log"), "r+") as logs:
log_id = self.live_info["started_at"] + " - " + self.username + " - " + self.live_info["title"]
for line in logs:
if log_id in line:
break
else:
logs.write(self.live_info["started_at"] + " - " + self.username + " - " + self.live_info["title"] + "\n")
self.sendNotif('Stream - ' + live_raw_filename, 'Streamer went live: ' + self.live_info["title"])
subprocess.call(['streamlink', 'twitch.tv/'+ self.username, self.quality, '--twitch-api-header', 'Authorization=OAuth ' + os.getenv('OAUTH-PRIVATE-TOKEN'), '--hls-segment-threads', str(self.hls_segments), '--hls-live-restart', '--retry-streams', str(self.refresh), '--twitch-disable-reruns', '-o', live_raw_path])
if(os.path.exists(live_raw_path) is True):
if self.os == 'windows': subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/ffmpeg.exe', '-y', '-i', live_raw_path, '-analyzeduration', '2147483647', '-probesize', '2147483647', '-c:v', 'copy', '-c:a', 'copy', '-start_at_zero', '-copyts', live_proc_path], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
elif self.os == 'linux': subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/ffmpeg', '-y', '-i', live_raw_path, '-analyzeduration', '2147483647', '-probesize', '2147483647', '-c:v', 'copy', '-c:a', 'copy', '-start_at_zero', '-copyts', live_proc_path], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
else:
print("Skip fixing. File not found.")
try:
vodurl = f'https://api.twitch.tv/helix/videos?user_id={str(self.channel_id)}&period=day&type=archive'
vods = requests.get(vodurl, headers = {"Authorization" : "Bearer " + self.oauth_token, "Client-ID": os.getenv('CLIENT-ID')}, timeout = 30)
vodsinfo = json.loads(vods.text)
if vodsinfo["data"][0] != []:
vod_date = datetime.strptime(vodsinfo["data"][0]["created_at"],'%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone('UTC')).astimezone(tz=None).replace(tzinfo=None)
vod_raw_filename = datetime.strftime(vod_date,'%Y%m%d_%Hh%Mm%Ss')
if self.live_info["id"] == vodsinfo["data"][0]["stream_id"]:
current_vod = vodsinfo["data"][0]
vod_raw_path = os.path.join(self.raw_path, "VOD_" + live_raw_filename + ".ts")
vod_proc_path = os.path.join(self.video_path, "VOD_" + live_raw_filename + ".mp4")
if self.downloadMETADATA == 1:
self.sendNotif('Metadata - ' + live_raw_filename,'Downloading and saving metadata:\n' + json.dumps(current_vod, indent=4))
with open(os.path.join(self.metadata_path, "METADA_" + live_raw_filename + ".json"), 'w', encoding='utf-8') as f:
json.dump(current_vod, f, ensure_ascii=False, indent=4)
if self.downloadVOD == 1:
print('Downloading VOD: ' + current_vod["title"])
self.sendNotif('VOD - ' + live_raw_filename,'Downloading VOD: ' + current_vod["title"])
try:
subprocess.call(['streamlink', 'twitch.tv/videos/' + current_vod["id"], self.quality, '--twitch-api-header', 'Authorization=OAuth ' + os.getenv('OAUTH-PRIVATE-TOKEN'), "--hls-segment-threads", str(self.hls_segmentsVOD), "-o", vod_raw_path])
if(os.path.exists(vod_raw_path) is True):
if self.os == 'windows':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/ffmpeg.exe', '-y', '-i', vod_raw_path, '-analyzeduration', '2147483647', '-probesize', '2147483647', '-c:v', 'copy', '-c:a', 'copy', '-start_at_zero', '-copyts', vod_proc_path], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
elif self.os == 'linux':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/ffmpeg', '-y', '-i', vod_raw_path, '-analyzeduration', '2147483647', '-probesize', '2147483647', '-c:v', 'copy', '-c:a', 'copy', '-start_at_zero', '-copyts', vod_proc_path], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
else:
print("Skip fixing. File not found.")
except Exception as e:
print('Error', 'A ERROR has ocurred and the VOD will not be downloaded.\n')
self.sendNotif('ERROR - ' + live_raw_filename, 'A ERROR has ocurred and the VOD will not be downloaded.\n')
if self.downloadCHAT == 1: live_raw_path = os.path.join(self.raw_path, "LIVE_" + live_raw_filename + ".ts")
print('Downloading and rendering CHAT: ' + current_vod["title"]) live_proc_path = os.path.join(self.video_path, "LIVE_" + live_raw_filename + ".mp4")
self.sendNotif('CHAT - ' + live_raw_filename,'Downloading JSON and rendering chat logs from VOD:\n' + current_vod["title"])
try: with open(os.path.join(self.root_path, ".log")) as logs:
if self.os == 'windows':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+"/bin/chat.bat", current_vod["id"], os.path.join(self.chatJSON_path, "CHAT_" + live_raw_filename + ".json"), os.path.join(self.chatMP4_path, "CHAT_" + live_raw_filename + ".mp4")]) logs = logs.read()
elif self.os == 'linux':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+"/bin/chat.sh", current_vod["id"], os.path.join(self.chatJSON_path, "CHAT_" + live_raw_filename + ".json"), os.path.join(self.chatMP4_path, "CHAT_" + live_raw_filename + ".mp4")]) log_id = is_live["createdAt"] + " - " + self.username + " - " + is_live["title"]
except Exception as e: if log_id in logs:
self.sendNotif('ERROR - ' + live_raw_filename, "A ERROR has ocurred and chat will need to be downloaded and rendered manually.\n") time.sleep(self.refresh)
print("A ERROR has ocurred and chat will need to be downloaded and rendered manually\n")
with open(os.path.join(self.root_path, ".log"), "r+") as logs:
log_id = is_live["createdAt"] + " - " + self.username + " - " + is_live["title"]
for line in logs:
if log_id in line:
break
else: else:
print('A ERROR has ocurred, the latest VOD doesnt match with the livestream, the VOD is not published\nThe VOD and chat will not be downloaded and rendered.\nThe current livestream date: ' + live_raw_filename + '\nThe VOD date: ' + vod_raw_filename) logs.write(is_live["createdAt"] + " - " + self.username + " - " + is_live["title"] +"\n")
self.sendNotif('ERROR - ' + live_raw_filename, 'A ERROR has ocurred, the latest VOD doesnt match with the livestream, the VOD is not published\nThe VOD and chat will not be downloaded and rendered.\nThe current livestream date: ' + live_raw_filename + '\nThe VOD date: ' + vod_raw_filename)
except Exception as e: self.sendNotif('Stream - ' + live_raw_filename, 'Streamer went live: ' + is_live["title"])
print('An error has occurred. VOD and chat will not be downloaded. Please check them manually.\n') subprocess.call(['streamlink', 'twitch.tv/'+ self.username, self.quality, '--twitch-api-header', 'Authorization=OAuth ' + os.getenv('OAUTH-PRIVATE-TOKEN'), '--hls-segment-threads', str(self.hls_segments), '--hls-live-restart', '--retry-streams', str(self.refresh), '--twitch-disable-reruns', '-o', live_raw_path])
self.sendNotif('ERROR - ' + live_raw_filename, 'An error has occurred. VOD and chat will not be downloaded. Please check them manually.\n') if(os.path.exists(live_raw_path) is True):
if self.os == 'windows': subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/ffmpeg.exe', '-y', '-i', live_raw_path, '-analyzeduration', '2147483647', '-probesize', '2147483647', '-c:v', 'copy', '-c:a', 'copy', '-start_at_zero', '-copyts', live_proc_path], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
if self.cleanRaw == 1: elif self.os == 'linux': subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/ffmpeg', '-y', '-i', live_raw_path, '-analyzeduration', '2147483647', '-probesize', '2147483647', '-c:v', 'copy', '-c:a', 'copy', '-start_at_zero', '-copyts', live_proc_path], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
print('Deleting raw files') else:
if(os.path.exists(live_raw_path) is True): os.remove(live_raw_path) print("Skip fixing. File not found.")
if self.downloadVOD == 1:
if(os.path.exists(os.path.join(self.raw_path, "VOD_" + live_raw_filename + ".ts")) is True): current_vod = self.get_vod()['data']['user']['videos']['edges'][0]['node']
os.remove(os.path.join(self.raw_path, "VOD_" + live_raw_filename + ".ts")) live_date_min = live_date - timedelta(minutes=1)
if self.uploadCloud == 1: live_date_max = live_date + timedelta(minutes=1)
if self.os == 'windows':
tree = subprocess.run(['powershell.exe','tree', f'{self.root_path}/{self.username}', '/f'], capture_output=True, text=True).stdout.split("\n",2)[2] vod_date = datetime.strptime(current_vod["recordedAt"],'%Y-%m-%dT%H:%M:%SZ').replace(tzinfo=timezone('UTC')).astimezone(tz=None).replace(tzinfo=None)
elif self.os == 'linux':
tree = subprocess.check_output(['tree', str(pathlib.Path(self.root_path).resolve())+'/'+self.username]).decode(sys.stdout.encoding) if live_date_min <= vod_date <= live_date_max:
print('Uploading the following files:\n' + tree) vod_raw_path = os.path.join(self.raw_path, "VOD_" + live_raw_filename + ".ts")
self.sendNotif("UPLOADING - " + live_raw_filename, 'Uploading the following files: \n' + tree) vod_proc_path = os.path.join(self.video_path, "VOD_" + live_raw_filename + ".mp4")
if self.os == 'windows':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/upload.bat', str(pathlib.Path(self.root_path).resolve()),self.username])
elif self.os == 'linux':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/upload.sh', str(pathlib.Path(self.root_path).resolve()),self.username]) if self.downloadMETADATA == 1:
if self.deleteFiles == 1: self.sendNotif('Metadata - ' + live_raw_filename,'Downloading and saving metadata:\n' + json.dumps(current_vod, indent=4))
self.sendNotif("DELETING - " + live_raw_filename, "Deleting the files from current seccion.") with open(os.path.join(self.metadata_path, "METADA_" + live_raw_filename + ".json"), 'w', encoding='utf-8') as f:
print(f'{Fore.RED}DELETING FILES{Style.RESET_ALL}') json.dump(current_vod, f, ensure_ascii=False, indent=4)
if self.cleanRaw == 0:
print(f'{Fore.RED}Deleting ' + live_raw_path + f'{Style.RESET_ALL}') if self.downloadVOD == 1:
os.remove(live_raw_path) print('Downloading VOD: ' + current_vod["title"])
print(f'{Fore.RED}Deleting ' + live_proc_path + f'{Style.RESET_ALL}') self.sendNotif('VOD - ' + live_raw_filename,'Downloading VOD: ' + current_vod["title"])
os.remove(live_proc_path) try:
if self.downloadVOD == 1: subprocess.call(['streamlink', 'twitch.tv/videos/' + str(current_vod["id"]), self.quality, '--twitch-api-header', 'Authorization=OAuth ' + os.getenv('OAUTH-PRIVATE-TOKEN'), "--hls-segment-threads", str(self.hls_segmentsVOD), "-o", vod_raw_path])
if(os.path.exists(os.path.join(self.raw_path, "VOD_" + live_raw_filename + ".ts")) is True): if(os.path.exists(vod_raw_path) is True):
if self.cleanRaw == 0: if self.os == 'windows':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/ffmpeg.exe', '-y', '-i', vod_raw_path, '-analyzeduration', '2147483647', '-probesize', '2147483647', '-c:v', 'copy', '-c:a', 'copy', '-start_at_zero', '-copyts', vod_proc_path], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
print(f'{Fore.RED}Deleting ' + os.path.join(self.raw_path, "VOD_" + live_raw_filename + ".ts") + f'{Style.RESET_ALL}') elif self.os == 'linux':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/ffmpeg', '-y', '-i', vod_raw_path, '-analyzeduration', '2147483647', '-probesize', '2147483647', '-c:v', 'copy', '-c:a', 'copy', '-start_at_zero', '-copyts', vod_proc_path], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
else:
print("Skip fixing. File not found.")
except Exception as e:
print('Error', 'A ERROR has ocurred and the VOD will not be downloaded.\n')
self.sendNotif('ERROR - ' + live_raw_filename, 'A ERROR has ocurred and the VOD will not be downloaded.\n')
if self.downloadCHAT == 1:
print('Downloading and rendering CHAT: ' + current_vod["title"])
self.sendNotif('CHAT - ' + live_raw_filename,'Downloading JSON and rendering chat logs from VOD:\n' + current_vod["title"])
try:
if self.os == 'windows':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+"/bin/chat.bat", str(current_vod["id"]), os.path.join(self.chatJSON_path, "CHAT_" + live_raw_filename + ".json"), os.path.join(self.chatMP4_path, "CHAT_" + live_raw_filename + ".mp4")])
elif self.os == 'linux':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+"/bin/chat.sh", str(current_vod["id"]), os.path.join(self.chatJSON_path, "CHAT_" + live_raw_filename + ".json"), os.path.join(self.chatMP4_path, "CHAT_" + live_raw_filename + ".mp4")])
except Exception as e:
self.sendNotif('ERROR - ' + live_raw_filename, "A ERROR has ocurred and chat will need to be downloaded and rendered manually.\n")
print("A ERROR has ocurred and chat will need to be downloaded and rendered manually\n")
else:
print('not VOD associated with stream found')
if self.cleanRaw == 1:
print('Deleting raw files')
if(os.path.exists(live_raw_path) is True): os.remove(live_raw_path)
if self.downloadVOD == 1:
if(os.path.exists(os.path.join(self.raw_path, "VOD_" + live_raw_filename + ".ts")) is True):
os.remove(os.path.join(self.raw_path, "VOD_" + live_raw_filename + ".ts")) os.remove(os.path.join(self.raw_path, "VOD_" + live_raw_filename + ".ts"))
if(os.path.exists(os.path.join(self.video_path, "VOD_" + live_raw_filename + ".mp4")) is True): if self.uploadCloud == 1:
print(f'{Fore.RED}Deleting ' + os.path.join(self.video_path, "VOD_" + live_raw_filename + ".mp4") + f'{Style.RESET_ALL}') with open(str(pathlib.Path(__file__).parent.resolve())+"/bin/temp/upload.txt", "a") as myfile:
os.remove(os.path.join(self.video_path, "VOD_" + live_raw_filename + ".mp4")) myfile.write("LIVE_" + live_raw_filename + ".ts\n"+"VOD_" + live_raw_filename + ".ts\n"+"LIVE_" + live_raw_filename + ".mp4\n"+"VOD_" + live_raw_filename + ".mp4\n"+"METADATA_" + live_raw_filename + ".json\n"+"CHAT_" + live_raw_filename + ".json\n"+"CHAT_" + live_raw_filename + ".mp4\n")
if self.downloadCHAT == 1: print('Uploading files')
if(os.path.exists(os.path.join(self.chatJSON_path, "CHAT_"+live_raw_filename + ".json")) is True): self.sendNotif("UPLOADING - " + live_raw_filename, 'The files are being uploaded')
print(f'{Fore.RED}Deleting ' + os.path.join(self.chatJSON_path, "CHAT_"+live_raw_filename + ".json") + f'{Style.RESET_ALL}') if self.os == 'windows':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/upload.bat', str(pathlib.Path(self.root_path).resolve()),self.username])
os.remove(os.path.join(self.chatJSON_path, "CHAT_"+live_raw_filename + ".json")) elif self.os == 'linux':subprocess.call([str(pathlib.Path(__file__).parent.resolve())+'/bin/upload.sh', str(pathlib.Path(self.root_path).resolve()),self.username])
if(os.path.exists(os.path.join(self.chatMP4_path, "CHAT_"+live_raw_filename + ".mp4")) is True): os.remove(str(pathlib.Path(__file__).parent.resolve())+"/bin/temp/upload.txt")
print(f'{Fore.RED}Deleting ' + os.path.join(self.chatMP4_path, "CHAT_"+live_raw_filename + ".mp4") + f'{Style.RESET_ALL}') if self.deleteFiles == 1:
os.remove(os.path.join(self.chatMP4_path, "CHAT_"+live_raw_filename + ".mp4")) self.sendNotif("DELETING - " + live_raw_filename, "Deleting the files from current seccion.")
if self.downloadMETADATA == 1: print(f'{Fore.RED}DELETING FILES{Style.RESET_ALL}')
if(os.path.exists(os.path.join(self.metadata_path, "METADA_"+live_raw_filename+".json")) is True): if self.cleanRaw == 0:
print(f'{Fore.RED}Deleting ' + os.path.join(self.metadata_path, "METADA_"+live_raw_filename+".json") + f'{Style.RESET_ALL}') print(f'{Fore.RED}Deleting ' + live_raw_path + f'{Style.RESET_ALL}')
os.remove(os.path.join(self.metadata_path, "METADA_"+live_raw_filename+".json")) os.remove(live_raw_path)
print('CURRENT SECCION HAVE FINISHED GOING BACK TO CHECKING') print(f'{Fore.RED}Deleting ' + live_proc_path + f'{Style.RESET_ALL}')
self.sendNotif("SECCION DONE - " + live_raw_filename, 'CURRENT SECCION HAVE FINISHED GOING BACK TO CHECKING') os.remove(live_proc_path)
time.sleep(self.refresh) if self.downloadVOD == 1:
if(os.path.exists(os.path.join(self.raw_path, "VOD_" + live_raw_filename + ".ts")) is True):
if self.cleanRaw == 0:
print(f'{Fore.RED}Deleting ' + os.path.join(self.raw_path, "VOD_" + live_raw_filename + ".ts") + f'{Style.RESET_ALL}')
os.remove(os.path.join(self.raw_path, "VOD_" + live_raw_filename + ".ts"))
if(os.path.exists(os.path.join(self.video_path, "VOD_" + live_raw_filename + ".mp4")) is True):
print(f'{Fore.RED}Deleting ' + os.path.join(self.video_path, "VOD_" + live_raw_filename + ".mp4") + f'{Style.RESET_ALL}')
os.remove(os.path.join(self.video_path, "VOD_" + live_raw_filename + ".mp4"))
if self.downloadCHAT == 1:
if(os.path.exists(os.path.join(self.chatJSON_path, "CHAT_"+live_raw_filename + ".json")) is True):
print(f'{Fore.RED}Deleting ' + os.path.join(self.chatJSON_path, "CHAT_"+live_raw_filename + ".json") + f'{Style.RESET_ALL}')
os.remove(os.path.join(self.chatJSON_path, "CHAT_"+live_raw_filename + ".json"))
if(os.path.exists(os.path.join(self.chatMP4_path, "CHAT_"+live_raw_filename + ".mp4")) is True):
print(f'{Fore.RED}Deleting ' + os.path.join(self.chatMP4_path, "CHAT_"+live_raw_filename + ".mp4") + f'{Style.RESET_ALL}')
os.remove(os.path.join(self.chatMP4_path, "CHAT_"+live_raw_filename + ".mp4"))
if self.downloadMETADATA == 1:
if(os.path.exists(os.path.join(self.metadata_path, "METADA_"+live_raw_filename+".json")) is True):
print(f'{Fore.RED}Deleting ' + os.path.join(self.metadata_path, "METADA_"+live_raw_filename+".json") + f'{Style.RESET_ALL}')
os.remove(os.path.join(self.metadata_path, "METADA_"+live_raw_filename+".json"))
print('CURRENT SECCION HAVE FINISHED GOING BACK TO CHECKING')
self.sendNotif("SECCION DONE - " + live_raw_filename, 'CURRENT SECCION HAVE FINISHED GOING BACK TO CHECKING')
time.sleep(self.refresh)
else: time.sleep(self.refresh)
else: time.sleep(self.refresh)
def main(argv): def main(argv):
twitch_archive = TwitchArchive() twitch_archive = TwitchArchive()
help_msg = 'Twitch-Archive\nPython script to record twitch live stream, download the VOD, metadata, chat and render it, and uploads them to any cloud storage.\n -h, --help Display this information\n -u, --username <username> Twitch channel username\n -q, --quality <quality> best/source high/720p medium/480p worst/360p\n -v, --vod <1/0> Download vod\n -c, --chat <1/0> Download chat and render it\n -m, --metadata <1/0> Download metadata\n -r, --upload <1/0> Upload to cloud storage\n -d, --delete <1/0> Delete all files after upload (CAREFUL with this arg)\n -n, --notifications <1/0> Receive email notification of the proccess through gmail\n' help_msg = 'Twitch-Archive\nPython script to record twitch live stream, download the VOD, metadata, chat and render it, and uploads them to any cloud storage.\n -h, --help Display this information\n -u, --username <username> Twitch channel username\n -q, --quality <quality> best/source high/720p medium/480p worst/360p\n -v, --vod <1/0> Download vod\n -c, --chat <1/0> Download chat and render it\n -m, --metadata <1/0> Download metadata\n -r, --upload <1/0> Upload to cloud storage\n -d, --delete <1/0> Delete all files after upload (CAREFUL with this arg)\n -n, --notifications <1/0> Receive email notification of the proccess through gmail\n'