add AutoPlayer
This commit is contained in:
parent
6af6d5259f
commit
54b308adcf
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -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()
|
||||
|
|
@ -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',
|
||||
)
|
||||
Loading…
Reference in New Issue