-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathstreamcapture.sh
More file actions
executable file
·273 lines (252 loc) · 14.9 KB
/
streamcapture.sh
File metadata and controls
executable file
·273 lines (252 loc) · 14.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#!/bin/bash
######CONFIGURATION######
#Enter your streamers seperated by spaces. If they have a space in their name, use quotes around their name.
twitchstreamers=(isthisrealvr Vrey Ikumi Ebiko Miyunie__ MiruneMochi)
kickstreamers=(blu-haze VreyVrey MikaMoonlight roflgator itskxtlyn momocita zayi)
#Enter a list of games you want to monitor the streamers for seperated by spaces. If there is a space in the name, use quotes around the name.
game=(VRChat ASMR)
#Do you only want to record if they are playing a specific game specified above? Set to 1 to enable game monitoring, or 0 to disable.
monitortwitchgame=1
monitorkickgame=1
#Do you want to stop recording if your streamer switches to a game not specified above?
stoptwitchrecord=1
stopkickrecord=1
#Destination path is where you'll save the recordings. The authorization file is for your Twitch API credentials, and the configfile is where it'll save your bearer token once it authenticates.
destpath="/Drobo/Hareis"
authorizationfile="/root/Mango/.twitchcreds.conf"
configfile="/root/Mango/.twitchrecord.conf"
# We need curl-impersonate. Otherwise we fall back to legacy mode which is not ideal. Having this allows better episode naming.
curlimp="/opt/curl-impersonate/curl_chrome116"
# Do we want to enable logging?
logging=2 #Start/Stop/Errors/etc -- 0 = No Logging -- 1 = Standard Logging -- 2 = +Error Logging
debug=0 #Streamlink & ffmpeg output -- 0 = No Logging -- 1 = Streamlink & ffmpeg logging
######CONFIGURATION######
#DEFINE COLORS
GREEN='\e[32m'
YELLOW='\e[33m'
RED='\e[31m'
BLUE='\e[96m'
NC='\e[0m'
fnLog(){
# $1 = level (LOCAL,VERBOSE,INFO,SUCCESS,WARN,STOP,ERROR), $2 = message
local level="$1" message="$2"
local now="$(date)"
case "$level" in
LOCAL) echo -e "[${BLUE}*${NC}] ${BLUE}$now${NC} - $message" ;;
VERBOSE) (( logging >= 2 )) && echo -e "[${BLUE}*${NC}] ${BLUE}$now${NC} - $message" ;;
INFO) (( logging >= 1 )) && echo -e "[${BLUE}*${NC}] ${BLUE}$now${NC} - $message" | tee -a "$destpath/logs/log.txt" ;;
SUCCESS) (( logging >= 1 )) && echo -e "[${GREEN}+${NC}] ${BLUE}$now${NC} - $message" | tee -a "$destpath/logs/log.txt" ;;
WARN) (( logging >= 1 )) && echo -e "[${YELLOW}/${NC}] ${BLUE}$now${NC} - $message" | tee -a "$destpath/logs/log.txt" ;;
STOP) (( logging >= 1 )) && echo -e "[${RED}-${NC}] ${BLUE}$now${NC} - $message" | tee -a "$destpath/logs/log.txt" ;;
ERROR) (( logging >= 2 )) && echo -e "[${RED}*${NC}] ${BLUE}$now${NC} - $message" | tee -a "$destpath/logs/errlog.txt" ;;
esac
}
fnDependencyCheck(){
local missdep=0
if [[ ! -d "$destpath" ]]; then
fnLog "ERROR" "Destination path \"$destpath\" does not exist or is inaccessible."
exit 1
fi
if [[ -d "$destpath" && ! -d "$destpath/logs" ]]; then
mkdir "$destpath/logs"
fnLog "ERROR" "Log directory does not exist... creating directory at \"$destpath/logs\""
fi
if [[ ! -x "$curlimp" ]]; then
fnLog "ERROR" "${BLUE}curl-impersonate${NC} not found at ${RED}$curlimp${NC}"
missdep=1
fi
if [[ ! $(command -v jq) ]]; then
fnLog "ERROR" "${BLUE}jq${NC} not found!"
missdep=1
fi
if [[ ! $(command -v screen) ]]; then
fnLog "ERROR" "${BLUE}screen${NC} not found!"
missdep=1
fi
if [[ ! $(command -v ffmpeg) ]]; then
fnLog "ERROR" "${BLUE}ffmpeg${NC} not found!"
missdep=1
fi
if [[ $(streamlink --can-handle-url https://www.kick.com/test) && ! $? == 0 ]]; then
fnLog "ERROR" "streamlink ${BLUE}kick plugin${NC} not found!"
missdep=1
fi
if [[ $missdep == 1 ]]; then
echo -en "[${RED}-${NC}] "
read -rsp "Dependencies missing... press ENTER key to continue or CTRL+C to exit."
fi
}
#Run a for loop on the streamers array so we can use multiple names to record.
fnStart(){
if [[ -n "${twitchstreamers[*]}" ]]; then
fnLog "LOCAL" "[${GREEN}---${NC}] Twitch: [${GREEN}---${NC}]"
for streamer in "${twitchstreamers[@]}"; do
unset kick
twitch=1
fnConfig
done
fi
if [[ -n "${kickstreamers[*]}" ]]; then
fnLog "LOCAL" "[${GREEN}---${NC}] Kick: [${GREEN}---${NC}]"
for streamer in "${kickstreamers[@]}"; do
unset twitch
retry=0
kick=1
fnConfig
done
fi
}
#Check to see if the config file exists. If there are issues with it, the fnRequestTwitch will delete it and start over.
fnConfig(){
if [[ ! -d "$destpath/$streamer" ]]; then
mkdir "$destpath/$streamer"
fi
if [[ "$twitch" == 1 ]]; then
service=Twitch
if [[ -z $(screen -list | grep "$streamer-$service") && -f "$destpath/logs/$streamer-$service.lock" ]]; then
fnLog "INFO" "${GREEN}$service:${NC} Unlocking ${BLUE}$streamer${NC}."
rm "$destpath/logs/$streamer-$service.lock"
fi
if [[ ! -f "$authorizationfile" ]]; then
fnLog "ERROR" "${RED}Twitch:${NC} Config file with authorization credentials missing!"
touch "$authorizationfile"
echo "clientid=" >> "$authorizationfile"
echo "clientsecret=" >> "$authorizationfile"
exit
else
source "$authorizationfile"
fi
if [[ -f "$configfile" ]]; then
source "$configfile"
fnRequestTwitch
else
fnAccessToken
fi
fi
if [[ "$kick" == 1 ]]; then
service=Kick
if [[ -z $(screen -list | grep "$streamer-$service") && -f "$destpath/logs/$streamer-$service.lock" ]]; then
if [[ $logging -ge 2 ]]; then
fnLog "INFO" "${GREEN}$service:${NC} Unlocking ${BLUE}$streamer${NC}."
fi
rm "$destpath/logs/$streamer-$service.lock"
fi
fnRequestKick
fi
}
#Request a new access token if the one from the config file can't be loaded or is expired.
fnAccessToken(){
if [[ $(echo "$request" | jq -r .message) == "Malformed query params." ]]; then
fnLog "ERROR" "${RED}Twitch:${NC} Streamer does not exist or another error has occured: $streamer"
return
fi
if [[ -z "$request" || $(echo "$request" | jq -r '.error') != "null" || -z "$access_token" ]]; then
#This is doing the oauth authentication and saving the bearer token that we receive to the config file.
access_token=$(curl -s -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=client_credentials&client_id=$clientid&client_secret=$clientsecret" https://id.twitch.tv/oauth2/token | jq -r '.access_token')
if [[ ${#access_token} == 30 ]]; then
echo "access_token=$access_token" > "$configfile"
fnLog "ERROR" "${RED}Twitch:${NC} Pulled new access token!"
else
fnLog "ERROR" "${RED}Twitch:${NC} Error pulling new access token. Check your API credentials! Token response: $access_token"
fi
fi
}
#Make a request to the Twitch API for the streamers information.
fnRequestTwitch(){
request=$(curl -s -H "Client-Id: $clientid" -H "Authorization: Bearer $access_token" -X GET "https://api.twitch.tv/helix/streams?user_login=$streamer")
if [[ $(echo "$request" | jq -r .message) == "Malformed query params." ]]; then
fnLog "ERROR" "${RED}Twitch:${NC} Streamer does not exist or another error has occured: $streamer"
return
fi
if [[ $(echo "$request" | jq -r '.error') != "null" || -z "$access_token" ]]; then
#echo "Token Error: $(echo $request | jq -r '.error') ---- $access_token"
unset access_token
rm "$configfile"
fnConfig
elif [[ $(echo "$request" | jq -r .data[0].type) != "live" ]]; then
#If the streamer is not live, we can skip the rest of the checks.
fnLog "LOCAL" "${RED}Twitch:${NC} ${BLUE}$streamer${NC} is not live."
unset twitch
return
elif [[ -z $(ps -ef | grep -v grep | grep "https://www.twitch.tv/$streamer" | grep streamlink) && $(echo "$request" | jq -r '.data[].type') == "live" ]] && [[ " ${game[*]} " =~ " $(echo "$request" | jq -r '.data[]?.game_name // null') " || "$monitortwitchgame" == 0 ]]; then
#If we aren't already recording, and the game they're playing matches what we want to record, then start recording.
fnStartTwitchRecord
elif [[ -n $(ps -ef | grep -v grep | grep "https://www.twitch.tv/$streamer" | grep streamlink) && $(echo "$request" | jq -r '.data[].type') == "live" && ! " ${game[*]} " =~ " $(echo "$request" | jq -r '.data[]?.game_name // null') " && "$stoptwitchrecord" == 1 && "$monitortwitchgame" == 1 ]]; then
#If they change game and we have stop recording set, then stop recording.
stopgame=$(echo "$request" | jq -r '.data[]?.game_name // null')
fnStopRecord
elif [[ -n $(ps -ef | grep -v grep | grep "https://www.twitch.tv/$streamer" | grep streamlink) ]]; then
fnLog "LOCAL" "${GREEN}Twitch:${NC} ${BLUE}$streamer${NC} is live in ${YELLOW}$(echo "$request" | jq -r '.data[]?.game_name // null')${NC} and we're already recording."
elif [[ $(echo "$request" | jq -r '.data[].type') == "live" ]]; then
fnLog "LOCAL" "${YELLOW}Twitch:${NC} ${BLUE}$streamer${NC} is live in ${RED}$(echo "$request" | jq -r '.data[]?.game_name // null')${NC} which is not in ${YELLOW}${game[*]}${NC}."
else
fnLog "LOCAL" "${RED}Twitch:${NC} ${BLUE}$streamer${NC} we shouldn't be here..."
fi
unset twitch
}
fnRequestKick(){
request=$("$curlimp" -s "https://kick.com/api/v2/channels/$streamer")
while [[ "$request" =~ "Just a moment..." && "${retry:-0}" -le 5 ]]; do
fnLog "LOCAL" "${RED}Kick:${NC} ${BLUE}$streamer${NC}: Got CloudFlare response... trying again... $retry."
request=$("$curlimp" -s "https://kick.com/api/v2/channels/$streamer")
((retry++))
sleep 1
if [[ "$request" =~ "Just a moment..." && "${retry:-0}" -ge 5 ]]; then
fnLog "ERROR" "${RED}Kick:${NC} ${BLUE}$streamer${NC}: Got CloudFlare response we're unable to bypass..."
return
fi
done
if [[ $(echo "$request" | jq -r '.livestream.is_live') != "true" ]]; then
#If the streamer is not live, we can skip the rest of the checks.
fnLog "LOCAL" "${RED}Kick:${NC} ${BLUE}$streamer${NC} is not live."
unset kick
return
elif [[ -z $(ps -ef | grep -v grep | grep "https://www.kick.com/$streamer" | grep streamlink) && -z $(screen -list | grep "$streamer-$service") && $(echo "$request" | jq -r '.livestream.is_live') == "true" && -z $(streamlink --stream-url "https://kick.com/$streamer" | grep error) ]] && [[ " ${game[*]} " =~ " $(echo "$request" | jq -r '.livestream.categories[]?.name // null') " || "$monitorkickgame" == 0 ]]; then
#If we aren't already recording, and the game they're playing matches what we want to record, then start recording.
fnStartKickRecord
elif [[ -n $(ps -ef | grep -v grep | grep "https://www.kick.com/$streamer" | grep streamlink) && -n $(screen -list | grep "$streamer-$service") && $(echo "$request" | jq -r '.livestream.is_live') == "true" && ! " ${game[*]} " =~ " $(echo "$request" | jq -r '.livestream.categories[]?.name // null') " && "$stopkickrecord" == 1 && "$monitorkickgame" == 1 ]]; then
#If they change game and we have stop recording set, then stop recording.
stopgame=$(echo "$request" | jq -r '.livestream.categories[]?.name // null')
fnStopRecord
elif [[ -n $(ps -ef | grep -v grep | grep "https://www.kick.com/$streamer" | grep streamlink) || -n $(screen -list | grep "$streamer-$service") ]]; then
fnLog "LOCAL" "${GREEN}Kick:${NC} ${BLUE}$streamer${NC} is live in ${YELLOW}$(echo "$request" | jq -r '.livestream.categories[]?.name // null')${NC} and we're already recording."
elif [[ $(echo "$request" | jq -r '.livestream.is_live') == "true" ]]; then
fnLog "LOCAL" "${YELLOW}Kick:${NC} ${BLUE}$streamer${NC} is live in ${RED}$(echo "$request" | jq -r '.livestream.categories[]?.name // null')${NC} which is not in ${YELLOW}${game[*]}${NC}."
else
fnLog "LOCAL" "${RED}Kick:${NC} ${BLUE}$streamer${NC} we shouldn't be here..."
fi
unset kick
}
fnStartTwitchRecord(){
# Creates an output name of "streamer_S(two digit year)E(julian date)_stream title_[streamid]"
outputname=$(echo "$request" | jq -j --arg jdate "$(date +%j)" --arg ydate "$(date +%y)" --arg random "$RANDOM" '.data[].user_login," - S",$ydate,"E",$jdate," - ",.data[].title," [",.data[].id + $random,"]"' | tr -dc '[:print:]' | tr -d '<>:"/\\|?*' | tr -s " ")
fnLog "SUCCESS" "${GREEN}Twitch:${NC} Starting recording of ${BLUE}$streamer${NC} playing ${GREEN}$(echo "$request" | jq -r '.data[]?.game_name // null')${NC}. File name: ${YELLOW}$outputname.mp4${NC}"
if [[ $debug -ge 1 ]]; then
screen -dmS "$streamer-$service" -L -Logfile "$destpath/logs/$outputname.txt" bash -c "streamlink --stdout https://www.twitch.tv/$streamer best | ffmpeg -i - -movflags faststart -c copy \"$destpath/$streamer/$outputname.mp4\""
else
screen -dmS "$streamer-$service" bash -c "streamlink --stdout https://www.twitch.tv/$streamer best | ffmpeg -i - -movflags faststart -c copy \"$destpath/$streamer/$outputname.mp4\""
fi
}
fnStartKickRecord(){
# Creates an output name of "streamer_S(two digit year)E(julian date)_stream title_[streamid]"
outputname=$(echo "$request" | jq -j --arg jdate "$(date +%j)" --arg ydate "$(date +%y)" --arg random "$RANDOM" '.user.username," - S",$ydate,"E",$jdate," - ",.livestream.session_title," [",(.livestream.id|tostring) + $random,"]"' | tr -dc '[:print:]' | tr -d '<>:"/\\|?*' | tr -s " " )
fnLog "SUCCESS" "${GREEN}Kick:${NC} Starting recording of ${BLUE}$streamer${NC} playing ${GREEN}$(echo "$request" | jq -r '.livestream.categories[]?.name // null')${NC}. File name: ${YELLOW}$outputname.mp4${NC}"
if [[ $debug -ge 1 ]]; then
screen -dmS "$streamer-$service" -L -Logfile "$destpath/logs/$outputname.txt" bash -c "streamlink --stdout https://www.kick.com/$streamer best | ffmpeg -i - -movflags faststart -c copy \"$destpath/$streamer/$outputname.mp4\""
else
screen -dmS "$streamer-$service" bash -c "streamlink --stdout https://www.kick.com/$streamer best | ffmpeg -i - -movflags faststart -c copy \"$destpath/$streamer/$outputname.mp4\""
fi
}
fnStopRecord(){
#This sends a ctrl+c (SIGINT) to the screen to gracefully stop the recording.
if [[ ! -f "$destpath/logs/$streamer-$service.lock" ]]; then
fnLog "INFO" "${GREEN}$service:${NC} Locking ${BLUE}$streamer${NC}."
touch "$destpath/logs/$streamer-$service.lock"
fnLog "STOP" "${GREEN}$service:${NC} Stopping recording of ${BLUE}$streamer${NC}. ${RED}$stopgame${NC} not in ${GREEN}${game[*]}${NC}."
screen -S "$streamer-$service" -X stuff $'\003'
else
fnLog "INFO" "${GREEN}$service:${NC} ${YELLOW}Locked${NC}: Waiting for processing to finish for ${BLUE}$streamer${NC}."
fi
}
fnDependencyCheck
fnStart