Seems finished

This commit is contained in:
Vitalii Lebedynskyi
2022-08-17 16:51:05 +03:00
parent 173fcc098f
commit fdfd2c6135
3 changed files with 121 additions and 23 deletions

View File

@@ -5,17 +5,18 @@ import matplotlib.pyplot as plt
from datetime import datetime from datetime import datetime
from clipper.chat import CHAT_DIVIDER
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class ChatAnalyser: class ChatAnalyser:
def __init__(self, ignore_commands=True, ignored_users=["moobot", "nightbot"]): def __init__(self, ignore_commands=True, ignored_users=None):
if ignored_users is None:
ignored_users = ["moobot", "nightbot"]
self.ignored_users = ignored_users self.ignored_users = ignored_users
self.ignore_commands = ignore_commands self.ignore_commands = ignore_commands
def run(self, chat_file, peaks_output_file, peaks_output_chart): def run(self, chat_file, peaks_output_file, peaks_output_chart, start_time):
dates = self._read_message_dates(chat_file) dates = self._read_message_dates(chat_file)
messages_per_minute = self._group_dates(dates) messages_per_minute = self._group_dates(dates)
peaks = self._find_peeks(messages_per_minute, peaks_output_file, peaks_output_chart) peaks = self._find_peeks(messages_per_minute, peaks_output_file, peaks_output_chart)
@@ -31,19 +32,23 @@ class ChatAnalyser:
if not line: if not line:
break break
message_data = line.split(CHAT_DIVIDER) message_data = line.split("<~|~>")
if len(message_data) != 3: if len(message_data) != 3:
# Wrong line format # Wrong line format
continue continue
if message_data[1].lower() in self.ignore_commands: if message_data[1].lower() in self.ignored_users:
continue continue
if self.ignore_commands and message_data[2].startswith("!"): if self.ignore_commands and message_data[2].startswith("!"):
continue continue
date = message_data[0] date = message_data[0]
dates.append(self._parse_date(date)) try:
dates.append(self._parse_date(date))
except BaseException as e:
logger.error(e)
return dates return dates
def _parse_date(self, date_str): def _parse_date(self, date_str):
@@ -64,20 +69,39 @@ class ChatAnalyser:
def _find_peeks(self, messages_per_minute, peaks_output_file, peaks_output_chart): def _find_peeks(self, messages_per_minute, peaks_output_file, peaks_output_chart):
y_coordinates = list(messages_per_minute.values()) y_coordinates = list(messages_per_minute.values())
x_coordinates = list(messages_per_minute.keys()) x_coordinates = list(messages_per_minute.keys())
peak_indices = scipy.signal.find_peaks_cwt(np.array(y_coordinates), 1) peak_indices = scipy.signal.find_peaks_cwt(np.array(y_coordinates), 0.5)
x_hours = [x.split(" ")[1] for x in x_coordinates]
fig, ax = plt.subplots() fig, ax = plt.subplots()
ax.plot(x_hours, y_coordinates) ax.plot(range(0, len(y_coordinates), 1), y_coordinates)
fig.autofmt_xdate() plt.xlabel("Video Minutes")
plt.xlabel("Time") plt.ylabel("Message count")
plt.ylabel("Count")
plt.title("Stream chat reaction") plt.title("Stream chat reaction")
plt.savefig(peaks_output_chart) plt.savefig(peaks_output_chart)
peak_values = [x_coordinates[index] for index in peak_indices] start_time = None
if len(x_coordinates) > 0:
start_time = datetime.strptime(x_coordinates[0], "%Y-%m-%d %H:%M")
max_value = max(y_coordinates)
trash_hold_value = max_value * 0.75
filtered_values = [x_coordinates[index] for index in peak_indices if y_coordinates[index] > trash_hold_value]
with open(peaks_output_file, "w") as stream: with open(peaks_output_file, "w") as stream:
for peak in peak_values: for peak in filtered_values:
stream.writelines(f"{peak}\n") if start_time:
peak_time = datetime.strptime(peak, "%Y-%m-%d %H:%M")
diff = peak_time - start_time
minutes = divmod(diff.total_seconds() / 60, 60)
stream.writelines(f"{peak} -> {minutes}\n")
else:
stream.writelines(f"{peak}\n")
return peak_indices return peak_indices
if __name__ == "__main__":
anal = ChatAnalyser()
chat_file = "/Users/vetalll/Projects/Python/TwitchClipper/recorded/vovapain/17-08-2022_08-33-23/chat.txt"
out_file = "/Users/vetalll/Projects/Python/TwitchClipper/recorded/vovapain/17-08-2022_08-33-23/chat_peaks.txt"
out_hraph = "/Users/vetalll/Projects/Python/TwitchClipper/recorded/vovapain/17-08-2022_08-33-23/chat_chart.png"
anal.run(chat_file, out_file, out_hraph, datetime(2022, 8, 15, 20, 38, 49))

View File

@@ -1,3 +1,72 @@
import logging
import os
import subprocess
from datetime import datetime
from datetime import timedelta
logger = logging.getLogger(__name__)
class Clipper: class Clipper:
def run(self): def run(self, video_file, chat_peaks_file, output_folder):
pass try:
self._run(video_file, chat_peaks_file, output_folder)
except BaseException as e:
logger.error(e)
def _run(self, video_file, chat_peaks_file, output_folder):
if not os.path.isdir(output_folder):
os.mkdir(output_folder)
with open(chat_peaks_file, "r") as stream:
lines = stream.readlines()
if not lines:
logger.error("No peaks found")
return
counter = 1
for line in lines:
# l = "2022-08-17 10:15 -> (1.0, 42.0)"
time_part = line.split("->")[1].strip() # (1.0, 42.0)
time = time_part.replace("(", "").replace(")", "").split(",")
video_time = datetime(2000, 1, 1, int(float(time[0])), int(float(time[1])), 0, 0)
start_time = video_time - timedelta(minutes=1)
end_time = video_time + timedelta(minutes=1)
ffmpeg_start_time = start_time.strftime("%H:%M:00")
ffmpeg_end_time = end_time.strftime("%H:%M:00")
output_file = os.path.join(output_folder, f"clip_{counter}.mp4")
self._cut_clip(video_file, ffmpeg_start_time, ffmpeg_end_time, output_file)
counter = counter + 1
def _cut_clip(self, video_file, start_time, end_time, output_name):
# ffmpeg -ss 00:01:00 -to 00:02:00 -i input.mp4 -c copy output.mp4
try:
subprocess.call([
"ffmpeg",
"-err_detect",
"ignore_err",
"-ss",
start_time,
"-to",
end_time,
"-i",
video_file,
"-c",
"copy",
output_name
])
except BaseException as e:
logger.error("Unable to run streamlink")
logger.error(e)
if __name__ == "__main__":
"/Users/vetalll/Projects/Python/TwitchClipper/recorded/"
video = "/Users/vetalll/Projects/Python/TwitchClipper/recorded/icebergdoto/17-08-2022_13-30-20/video.mp4"
peaks = "/Users/vetalll/Projects/Python/TwitchClipper/recorded/icebergdoto/17-08-2022_13-30-20/chat_peaks.txt"
result = "/Users/vetalll/Projects/Python/TwitchClipper/recorded/icebergdoto/17-08-2022_13-30-20/clips"
clipper = Clipper()
clipper.run(video, peaks, result)

View File

@@ -7,6 +7,7 @@ from datetime import datetime
from clipper.analyser import ChatAnalyser from clipper.analyser import ChatAnalyser
from clipper.api import TwitchApi, TwitchStreamStatus from clipper.api import TwitchApi, TwitchStreamStatus
from clipper.chat import TwitchChatRecorder from clipper.chat import TwitchChatRecorder
from clipper.clipper import Clipper
from clipper.video import TwitchVideoRecorder from clipper.video import TwitchVideoRecorder
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -29,6 +30,7 @@ class Recorder:
self.video_recorder = TwitchVideoRecorder() self.video_recorder = TwitchVideoRecorder()
self.chat_recorder = TwitchChatRecorder(self.api, debug=True) self.chat_recorder = TwitchChatRecorder(self.api, debug=True)
self.chat_analyser = ChatAnalyser() self.chat_analyser = ChatAnalyser()
self.clipper = Clipper()
def run(self): def run(self):
logger.info("Start recording streamer %s", self.config.tw_streamer) logger.info("Start recording streamer %s", self.config.tw_streamer)
@@ -45,13 +47,11 @@ class Recorder:
output_video_file = os.path.join(record_folder, "video.mp4") output_video_file = os.path.join(record_folder, "video.mp4")
output_chat_file = os.path.join(record_folder, "chat.txt") output_chat_file = os.path.join(record_folder, "chat.txt")
output_chat_peaks_file = os.path.join(record_folder, "chat_peaks.txt")
output_chat_chart_file = os.path.join(record_folder, "chat_chart.png")
self.chat_recorder.run(self.config.tw_streamer, output_chat_file) self.chat_recorder.run(self.config.tw_streamer, output_chat_file)
self.video_recorder.run(self.config.tw_streamer, output_video_file, quality="160p") self.video_recorder.run(self.config.tw_streamer, output_video_file, quality="160p")
self._loop_recording() self._loop_recording()
self._post_process_video(output_chat_file, output_chat_peaks_file, output_chat_chart_file) self._post_process_video(record_folder, output_chat_file, output_video_file, start_time)
elif status == TwitchStreamStatus.OFFLINE: elif status == TwitchStreamStatus.OFFLINE:
logger.info("Streamer %s is offline. Waiting for 300 sec", self.config.tw_streamer) logger.info("Streamer %s is offline. Waiting for 300 sec", self.config.tw_streamer)
@@ -77,7 +77,12 @@ class Recorder:
continue continue
break break
def _post_process_video(self, output_chat_file, output_chat_peaks_file, output_chat_chart_file): def _post_process_video(self, record_folder, output_chat_file, output_video_file, start_time):
output_chat_peaks_file = os.path.join(record_folder, "chat_peaks.txt")
output_chat_chart_file = os.path.join(record_folder, "chat_chart.png")
logger.info("Start looking for peaks in file %s", output_chat_file) logger.info("Start looking for peaks in file %s", output_chat_file)
peaks = self.chat_analyser.run(output_chat_file, output_chat_peaks_file, output_chat_chart_file) peaks = self.chat_analyser.run(output_chat_file, output_chat_peaks_file, output_chat_chart_file, start_time)
logger.info("Found peaks: %s for file %s", len(peaks), output_chat_file) logger.info("Found peaks: %s for file %s", len(peaks), output_chat_file)
self.clipper.run(output_video_file, output_chat_peaks_file, record_folder)