-
-
Save junian/b41dd8e544bf0e3980c971b0d015f5f6 to your computer and use it in GitHub Desktop.
# This code is based on tutorial by slicktechies modified as needed to use oauth token from Twitch. | |
# You can read more details at: https://www.junian.net/2017/01/how-to-record-twitch-streams.html | |
# original code is from https://slicktechies.com/how-to-watchrecord-twitch-streams-using-livestreamer/ | |
import requests | |
import os | |
import time | |
import json | |
import sys | |
import subprocess | |
import datetime | |
import getopt | |
class TwitchRecorder: | |
def __init__(self): | |
# global configuration | |
self.client_id = "jzkbprff40iqj646a697cyrvl0zt2m6" # don't change this | |
# get oauth token value by typing `streamlink --twitch-oauth-authenticate` in terminal | |
self.oauth_token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | |
self.ffmpeg_path = 'ffmpeg' | |
self.refresh = 30.0 | |
self.root_path = "/Users/junian/Documents/twitch" | |
# user configuration | |
self.username = "juniantr" | |
self.quality = "best" | |
def run(self): | |
# path to recorded stream | |
self.recorded_path = os.path.join(self.root_path, "recorded", self.username) | |
# path to finished video, errors removed | |
self.processed_path = os.path.join(self.root_path, "processed", self.username) | |
# create directory for recordedPath and processedPath if not exist | |
if(os.path.isdir(self.recorded_path) is False): | |
os.makedirs(self.recorded_path) | |
if(os.path.isdir(self.processed_path) is False): | |
os.makedirs(self.processed_path) | |
# make sure the interval to check user availability is not less than 15 seconds | |
if(self.refresh < 15): | |
print("Check interval should not be lower than 15 seconds.") | |
self.refresh = 15 | |
print("System set check interval to 15 seconds.") | |
# fix videos from previous recording session | |
try: | |
video_list = [f for f in os.listdir(self.recorded_path) if os.path.isfile(os.path.join(self.recorded_path, f))] | |
if(len(video_list) > 0): | |
print('Fixing previously recorded files.') | |
for f in video_list: | |
recorded_filename = os.path.join(self.recorded_path, f) | |
print('Fixing ' + recorded_filename + '.') | |
try: | |
subprocess.call([self.ffmpeg_path, '-err_detect', 'ignore_err', '-i', recorded_filename, '-c', 'copy', os.path.join(self.processed_path,f)]) | |
os.remove(recorded_filename) | |
except Exception as e: | |
print(e) | |
except Exception as e: | |
print(e) | |
print("Checking for", self.username, "every", self.refresh, "seconds. Record with", self.quality, "quality.") | |
self.loopcheck() | |
def check_user(self): | |
# 0: online, | |
# 1: offline, | |
# 2: not found, | |
# 3: error | |
url = 'https://api.twitch.tv/kraken/streams/' + self.username | |
info = None | |
status = 3 | |
try: | |
r = requests.get(url, headers = {"Client-ID" : self.client_id}, timeout = 15) | |
r.raise_for_status() | |
info = r.json() | |
if info['stream'] == None: | |
status = 1 | |
else: | |
status = 0 | |
except requests.exceptions.RequestException as e: | |
if e.response: | |
if e.response.reason == 'Not Found' or e.response.reason == 'Unprocessable Entity': | |
status = 2 | |
return status, info | |
def loopcheck(self): | |
while True: | |
status, info = self.check_user() | |
if status == 2: | |
print("Username not found. Invalid username or typo.") | |
time.sleep(self.refresh) | |
elif status == 3: | |
print(datetime.datetime.now().strftime("%Hh%Mm%Ss")," ","unexpected error. will try again in 5 minutes.") | |
time.sleep(300) | |
elif status == 1: | |
print(self.username, "currently offline, checking again in", self.refresh, "seconds.") | |
time.sleep(self.refresh) | |
elif status == 0: | |
print(self.username, "online. Stream recording in session.") | |
filename = self.username + " - " + datetime.datetime.now().strftime("%Y-%m-%d %Hh%Mm%Ss") + " - " + (info['stream']).get("channel").get("status") + ".mp4" | |
# clean filename from unecessary characters | |
filename = "".join(x for x in filename if x.isalnum() or x in [" ", "-", "_", "."]) | |
recorded_filename = os.path.join(self.recorded_path, filename) | |
# start streamlink process | |
subprocess.call(["streamlink", "--twitch-oauth-token", self.oauth_token, "twitch.tv/" + self.username, self.quality, "-o", recorded_filename]) | |
print("Recording stream is done. Fixing video file.") | |
if(os.path.exists(recorded_filename) is True): | |
try: | |
subprocess.call([self.ffmpeg_path, '-err_detect', 'ignore_err', '-i', recorded_filename, '-c', 'copy', os.path.join(self.processed_path, filename)]) | |
os.remove(recorded_filename) | |
except Exception as e: | |
print(e) | |
else: | |
print("Skip fixing. File not found.") | |
print("Fixing is done. Going back to checking..") | |
time.sleep(self.refresh) | |
def main(argv): | |
twitch_recorder = TwitchRecorder() | |
usage_message = 'twitch-recorder.py -u <username> -q <quality>' | |
try: | |
opts, args = getopt.getopt(argv,"hu:q:",["username=","quality="]) | |
except getopt.GetoptError: | |
print (usage_message) | |
sys.exit(2) | |
for opt, arg in opts: | |
if opt == '-h': | |
print(usage_message) | |
sys.exit() | |
elif opt in ("-u", "--username"): | |
twitch_recorder.username = arg | |
elif opt in ("-q", "--quality"): | |
twitch_recorder.quality = arg | |
twitch_recorder.run() | |
if __name__ == "__main__": | |
main(sys.argv[1:]) |
@Kampfsemmel Thanks for letting us know.
@Kampfsemmel Thanks for letting us know.
@LPLe21
You can use my version of script. It has more features and has no delay before recording.
@EnterGin Thanks for the link. I think the 410 Client Error was caused by my install of Streamlink 1.3.0.
@LPLe21 yes, update Streamlink.
hello, since yesterday 2020/06/05, the code doesn't work anymore
got status 3: error causing unexpected error
i think its because the kraken API used won't accept twitch display name as self.username anymore, it requires channel "_id" instead
john@WisH:~/streamlink$ python3 twitch-recorder.py
Checking for verizon every 15.1 seconds. Record with best quality.
09h11m06s unexpected error. will try again in 5 minutes.
confirming what @z7wish7z posted.
Third confirmation, it stopped working for me on June 4th 2020 with the same error. Are there any alternatives or is it possible to (easily) change this code?
@z7wish7z @starvald @VicVaccuum I have my own version of this script, migrated to new twitch helix API:
https://github.com/ancalentari/twitch-stream-recorder
This one does not work anymore with old kraken API
@ancalentari
Now I already made an account at Git and someone was faster :) Thanks for the fix, now I don't need to post mine anymore.
@ancalentari
Do you know how to get your script working on windows? I'm not super linux savvy so I'm not sure what steps to take. Thank you for your script nonetheless.
@maholdi
You should be able to use exact same command except on windows when you install python, it's not going to be python3.8
but python
so:
python twitch-recorder.py --username forsen
You should be able to do it from cmd once you change the directory to where the script is.
If its a fresh install you might need to install requests
module this one with python -m pip install requests
or something like that.
Before you run the scrip also make sure streamlink is at least 1.4.1
streamlink --version-check
If the result is not:
[cli][info] Your Streamlink version (1.4.1) is up to date!
Then install newest streamlink. (The older one uses kraken api and thats not going to work).
If you have older versions of python on your windows, you might need to play around with your env variables as well.
Hey
Id like to use this Recorder for twitch. But i may need some help setting it up. Would anyone mind or be able to help me out here?
thanks in advance :D
@LPLe21
Yes, I can confirm that. Twitch recently started changing many things and the Kraken API is one of them.
Other software has similar problems. Twitch Leecher for example shows the exact same error 410.
I don't think we can do anything about it until someone engineers his way back into their new API.