From 54b308adcf28da60f1dea0233912c73492b8e068 Mon Sep 17 00:00:00 2001 From: space2lim Date: Sun, 1 Sep 2024 20:00:59 +0900 Subject: [PATCH] add AutoPlayer --- autoplayer/favicon.ico | Bin 0 -> 1150 bytes autoplayer/main.py | 257 +++++++++++++++++++++++++++++++++++++++++ autoplayer/main.spec | 38 ++++++ 3 files changed, 295 insertions(+) create mode 100644 autoplayer/favicon.ico create mode 100644 autoplayer/main.py create mode 100644 autoplayer/main.spec diff --git a/autoplayer/favicon.ico b/autoplayer/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f7ba25d0fc2757a91818456464dc36aff833d152 GIT binary patch literal 1150 zcmd6nOGs2v7{^c1P6CN)QPIOi_LaY@+Y?H5qBx2w)2_d;vamK69<-mUusu~70B6p+86s^t8NnDN0qv~0! zCBHaSb@9xsB^JY%@|u=p)G*?j*tYU)5JFwVQK0y=GJJgqp$ zaG%rU4Bes5^SBBH?bp`Oeq#&uxq{Y`tx{u*`Oqvq_U$;Wg62yJRG*Efc%PuNCSTW> z9`*3NbQ8+`{e*&_bnJs-=U$G$UAYIJ(#Mlq7A6MlIyXNT#BIx{aVJyI-B6(Sdm0NV+AFwxIF3Avz*TjDFYi5cmu|$@c!;0DDS|WO zym@q*hT>%UUU$)2oP_PTV7TAOTqwxc+b-&r=8Bq^qyEx5 Y4BwjT$v$K4rJ4)s|E`Mu2mUty1{@=S?EnA( literal 0 HcmV?d00001 diff --git a/autoplayer/main.py b/autoplayer/main.py new file mode 100644 index 0000000..586ceb9 --- /dev/null +++ b/autoplayer/main.py @@ -0,0 +1,257 @@ +import os +import re +import sys +import time +import requests +import threading +import tkinter as tk +from tkinter import ttk +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException, WebDriverException, NoSuchElementException + +class Main: + def __init__(self): + self.driver = webdriver.Chrome() + self.isRunning = False + self.init_ui() + + # UI 종료 이벤트 핸들러 + self.root.protocol("WM_DELETE_WINDOW", self.on_closing) + + # 브라우저 상태 감시 스레드 시작 + threading.Thread(target=self.monitor_browser, daemon=True).start() + + def init_ui(self): + self.root = tk.Tk() + self.center_window(self.root, 350, 200) + self.root.title("대학 e러닝 AutoPlayer") + self.root.geometry("350x200") + self.root.configure(bg='#F0F0F0') + icon_path = self.resource_path('favicon.ico') + self.root.iconbitmap(icon_path) + + style = ttk.Style() + style.configure('TButton', font=('Helvetica', 12), padding=10) + style.configure('TLabel', font=('Helvetica', 12), background='#F0F0F0') + style.configure('TProgressbar', thickness=20) + + self.label = ttk.Label(self.root, text="강의 주차로 이동 후 실행", anchor='center') + self.label.pack(pady=20) + + self.progress = ttk.Progressbar(self.root, orient="horizontal", length=300, mode="determinate") + self.progress.pack(pady=20) + + self.btn_run = ttk.Button(self.root, text="Run", command=self.start_thread) + self.btn_run.pack(pady=10) + + def center_window(self, root, width, height): + screen_width = root.winfo_screenwidth() + screen_height = root.winfo_screenheight() + x = (screen_width / 2) - (width / 2) + y = (screen_height / 2) - (height / 2) + root.geometry(f'{width}x{height}+{int(x)}+{int(y)}') + + def startup(self): + # self.center_browser() + self.driver.get('https://selc.or.kr/lms/main/MainView.do') + self.root.mainloop() + + def center_browser(self): + screen_width = self.driver.execute_script("return window.screen.availWidth;") + screen_height = self.driver.execute_script("return window.screen.availHeight;") + window_width = 1200 + window_height = 800 + x = (screen_width / 2) - (window_width / 2) + y = (screen_height / 2) - (window_height / 2) + self.driver.set_window_size(window_width, window_height) + self.driver.set_window_position(int(x), int(y)) + + def start_thread(self): + if self.isRunning: + return + self.isRunning = True + self.btn_run.config(state=tk.DISABLED) + self.label.config(text="자막 추출 준비 중...") + threading.Thread(target=self.run).start() + + def run(self): + try: + self.extract() + finally: + self.driver.switch_to.default_content() + self.isRunning = False + self.btn_run.config(state=tk.NORMAL) + self.progress.stop() + + def lv_1(self): # popCourseContent + self.driver.switch_to.default_content() + iframe = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.ID, "popCourseContent")) + ) + self.driver.switch_to.frame(iframe) + print('#=========이동함: popCourseContent') + + def lv_2(self): # learning_active + self.lv_1() + iframe = WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.ID, "learning_active")) + ) + self.driver.switch_to.frame(iframe) + print('#=========이동함: learning_active') + + def clean_subtitle(self, vtt_content): + lines = vtt_content.splitlines() + if lines[0].strip() == "WEBVTT": + lines = lines[1:] + timeline_pattern = re.compile(r'\d{2}:\d{2}:\d{2}\.\d{3} --> \d{2}:\d{2}:\d{2}\.\d{3}') + cleaned_lines = [line for line in lines if not timeline_pattern.match(line) and line.strip() != ''] + cleaned_content = '\n'.join(cleaned_lines) + return cleaned_content + + def fs_write(self, data, file_name): + base_path = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(__file__) + save_path = os.path.join(base_path, file_name+'.txt') + with open(save_path, 'w', encoding='utf-8') as file: + file.write(data) + print(f"Subtitles saved to {save_path}") + self.label.config(text=f"추출 완료: {save_path}") + + def alert_accept(self): + try: + WebDriverWait(self.driver, 3).until(EC.alert_is_present()) + alert = self.driver.switch_to.alert + print(f"Alert text: {alert.text}") + alert.accept() + except TimeoutException: + print("No alert appeared within the specified time") + + def extract(self): + pattern = re.compile(r"fncLearningWindow\(([^,]+),([^,]+),([^,]+),([^,]+),'LV'[^)]*,'N'[^)]*\)") + + # 페이지 확인 + try: + weekContent = self.driver.find_element(By.CSS_SELECTOR, '#contBody > div.module_quick') + except NoSuchElementException: + print("올바르지 않은 페이지입니다.") + self.label.config(text="올바르지 않은 페이지입니다.") + self.isRunning = False + self.btn_run.config(state=tk.NORMAL) + return + + # 미완료된 주차 추출 + left_weeks = [i.get_attribute("onclick") for i in weekContent.find_elements(By.CSS_SELECTOR, 'ul > li > a.ing')] + + # 반복 시작 + while len(left_weeks) != 0: + # 미완료 주차 목록 중 첫 번째 미완료 주차로 이동 + self.driver.execute_script(left_weeks[0]) + + # 첫 번째 Video 탐색 중 + lectures = self.driver.find_elements(By.CSS_SELECTOR, "div.btn_lecture_view > a") + first_vid = [match.group() for lecture in lectures if (match := pattern.search(lecture.get_attribute("onclick")))][0] + + # 첫 번째 Video 실행 + self.driver.execute_script(first_vid) + + # Video 플레이어 Iframe 이동 + self.lv_1() + WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.CSS_SELECTOR, "#pop_body > div.learn_left_menu > div > div.overflow_area")) + ) + self.lv_2() + WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.CSS_SELECTOR, "#player_html5_api")) + ) + + # 자막 파일 추출 시작 (반복문) ====================== + self.lv_1() + learnings = self.driver.find_elements(By.CSS_SELECTOR, "a[href*='javascript:fncLearningWindow']") + vids = [match.group() for vid in learnings if (match := pattern.search(vid.get_attribute("href")))] + self.progress["maximum"] = len(vids) # 프로그레스 바 최대값을 비디오 개수로 설정 + + for i, vid in enumerate(vids): + self.lv_1() + print(f"- {i + 1} 번째 영상 학습 중") + self.label.config(text=f"학습 중 ({i + 1} / {len(vids)})") + self.driver.execute_script(vid) + self.alert_accept() + + self.lv_2() + WebDriverWait(self.driver, 10).until( + EC.presence_of_element_located((By.CSS_SELECTOR, "#player_html5_api")) + ) + + # 비디오 재생이 끝날 때까지 현재 시간을 체크합니다. + while True: + try: + vjs_progress = self.driver.find_element(By.CSS_SELECTOR, "#player > div.vjs-control-bar > div.vjs-progress-control.vjs-control > div") + except NoSuchElementException: + print("videojs가 감지되지 않습니다.") + self.label.config(text="videojs가 감지되지 않습니다.") + self.isRunning = False + self.btn_run.config(state=tk.NORMAL) + return + now, total = vjs_progress.get_attribute("aria-valuetext").split(' of ') + print(f"현재: {now} | 총: {total}") + if now == total: + print("비디오 재생이 끝났습니다.") + self.label.config(text=f"학습 완료 ({i + 1} / {len(vids)}), 다음으로 이동") + time.sleep(2) + break + time.sleep(1) # 1초 간격으로 체크합니다.` + + # 각 비디오 처리 후 프로그레스 바 업데이트 + self.progress["value"] += 1 + + # 영상 종료 + self.lv_1() + self.driver.execute_script( + self.driver.find_element(By.CSS_SELECTOR, "body > div.l_popup_learn > div.title_box > div.btn_func > a").get_attribute("onclick") + ) + self.alert_accept() + self.progress["value"] = self.progress["maximum"] # 완료 시 프로그레스 바 최대값으로 설정 + + self.driver.switch_to.default_content() + try: + weekContent = self.driver.find_element(By.CSS_SELECTOR, '#contBody > div.module_quick') + except NoSuchElementException: + print("올바르지 않은 페이지입니다.") + self.label.config(text="올바르지 않은 페이지입니다.") + self.isRunning = False + self.btn_run.config(state=tk.NORMAL) + return + left_weeks = [i.get_attribute("onclick") for i in weekContent.find_elements(By.CSS_SELECTOR, 'ul > li > a.ing:not(.active)')] + + def monitor_browser(self): + while True: + time.sleep(1) + try: + self.driver.title # 브라우저가 열려 있는지 확인하기 위해 속성을 접근 + except WebDriverException: + print("브라우저가 닫혔습니다.") + self.root.quit() # UI 종료 + break + + def on_closing(self): + if self.isRunning: + self.driver.quit() + else: + self.driver.quit() + self.root.destroy() + + def resource_path(self, relative_path): + """ Get absolute path to resource, works for dev and for PyInstaller """ + try: + # PyInstaller creates a temp folder and stores path in _MEIPASS + base_path = sys._MEIPASS + except Exception: + base_path = os.path.abspath(".") + + return os.path.join(base_path, relative_path) + +if __name__ == "__main__": + process = Main() + process.startup() diff --git a/autoplayer/main.spec b/autoplayer/main.spec new file mode 100644 index 0000000..2afc9ea --- /dev/null +++ b/autoplayer/main.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + +a = Analysis( + ['main.py'], + pathex=[], + binaries=[], + datas=[('favicon.ico', '.')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='e러닝 AutoPlayer', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon='favicon.ico', +)