Hyukwoo Kim

file registration

1 +# -*- coding: utf-8 -*-
2 +
3 +# Form implementation generated from reading ui file 'Aditor_Ui_1.1.ui'
4 +#
5 +# Created by: PyQt5 UI code generator 5.15.1
6 +#
7 +# WARNING: Any manual changes made to this file will be lost when pyuic5 is
8 +# run again. Do not edit this file unless you know what you are doing.
9 +
10 +
11 +from PyQt5 import QtCore, QtGui, QtWidgets
12 +from PyQt5.QtWidgets import QApplication, QMainWindow, QMainWindow, QFileDialog, QVBoxLayout, QInputDialog, QMessageBox, QAction
13 +import sys
14 +import cv2
15 +from PyQt5.QtCore import QThread
16 +import numpy as np
17 +import pandas as pd
18 +from time import sleep
19 +from threading import Timer, Thread, Event
20 +from matplotlib.figure import Figure
21 +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
22 +
23 +
24 +
25 +class perpetualTimer():#정해진 시간마다(주기) function수행
26 +
27 + def __init__(self, t, hFunction):
28 + self.t = t
29 + self.hFunction = hFunction
30 + self.thread = Timer(self.t, self.handle_function)
31 + print("timer initate")
32 +
33 + def handle_function(self):
34 + self.hFunction()
35 + self.thread = Timer(self.t, self.handle_function)
36 + self.thread.start()
37 +
38 + def start(self):
39 + self.thread.start()
40 +
41 + def cancel(self):
42 + self.thread.cancel()
43 +
44 +
45 +class workerThread(QThread):
46 + updatedM = QtCore.pyqtSignal(int)
47 +
48 + def __init__(self, mw):
49 + self.mw = mw
50 + #print("thread initialized")
51 + QThread.__init__(self)
52 +
53 + def __del__(self):
54 + self.wait()
55 +
56 + def run(self):
57 + # print("run")
58 + itr = 0
59 + QApplication.processEvents()
60 + while self.mw.isRun:
61 + itr += 1
62 + # print("waiting")
63 + #print("frameID"+str(self.mw.frameID))
64 + #print("endframe"+str(self.mw.endframe))
65 + if self.mw.isthreadActive and self.mw.isbusy == False and self.mw.frameID != self.mw.cap.get(
66 + cv2.CAP_PROP_POS_FRAMES):
67 + # print(itr)\
68 + #print("in 재생중")
69 + if np.abs(self.mw.frameID - self.mw.cap.get(cv2.CAP_PROP_POS_FRAMES)) > 1:
70 + self.mw.cap.set(cv2.CAP_PROP_POS_FRAMES, self.mw.frameID)
71 +
72 + if self.mw.frameID >= self.mw.endframe-1: #끝에서 멈춰주는 기능
73 + if not self.mw.timer is None:
74 + print("end")
75 + print(self.mw.frameID)
76 + print(self.mw.horizontalSlider.value())
77 + self.mw.timer.cancel()
78 + self.mw.startButton.setEnabled(True)
79 + self.mw.pauseButton.setEnabled(False)
80 + self.mw.isthreadActive = False
81 + self.mw.frameID = self.mw.endframe-1
82 +
83 + if self.mw.timer is None:
84 + self.mw.frameID += 1
85 +
86 + self.mw.isbusy = True
87 + sf = self.mw.scaleFactor
88 + ret, image = self.mw.cap.read()
89 + self.mw.limg = image
90 +
91 + if sf <= 0:
92 + self.mw.isbusy = False
93 + continue
94 +
95 + if ret == False:
96 + self.mw.isthreadActive = False
97 + self.mw.isbusy = False
98 + continue
99 +
100 + nchannel = image.shape[2]
101 + limg2 = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
102 +
103 + timg = cv2.resize(
104 + limg2, (int(sf * limg2.shape[1]), int(sf * limg2.shape[0])))
105 + limage = QtGui.QImage(timg.data, timg.shape[1], timg.shape[0], nchannel * timg.shape[1],
106 + QtGui.QImage.Format_RGB888)
107 +
108 + if self.mw.resizegoing == False:
109 + self.mw.label.setPixmap(QtGui.QPixmap(limage))
110 + # self.label.setPixmap(QtGui.QPixmap(limage))
111 +
112 + if not self.mw.sliderbusy and not self.mw.sliderbusy2:
113 + self.updatedM.emit(self.mw.frameID)
114 +
115 + QApplication.processEvents()
116 + self.mw.isbusy = False
117 + else:
118 + #print("in 멈춤!!!")
119 + if self.mw.isthreadActive and self.mw.timer is None:
120 + self.mw.frameID += 1
121 + sleep(1.0 / 50)
122 +
123 +
124 +class Ui_MainWindow(object):
125 +
126 + def setupUi(self, MainWindow):
127 + MainWindow.setObjectName("MainWindow")
128 + MainWindow.resize(801, 773)
129 + self.centralwidget = QtWidgets.QWidget(MainWindow)
130 + self.centralwidget.setMinimumSize(QtCore.QSize(801, 732))
131 + self.centralwidget.setObjectName("centralwidget")
132 + self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
133 + self.horizontalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 781, 41))
134 + self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
135 + self.horizontalLayout_4b = QtWidgets.QHBoxLayout(
136 + self.horizontalLayoutWidget)
137 + self.horizontalLayout_4b.setContentsMargins(0, 0, 0, 0)
138 + self.horizontalLayout_4b.setObjectName("horizontalLayout_4b")
139 + self.openVideoButton = QtWidgets.QPushButton(
140 + self.horizontalLayoutWidget)
141 + self.openVideoButton.setToolTip("편집할 비디오를 선택하고 엽니다.")
142 + self.openVideoButton.setObjectName("openVideoButton")
143 + self.horizontalLayout_4b.addWidget(self.openVideoButton)
144 + self.openDataButton = QtWidgets.QPushButton(self.horizontalLayoutWidget)
145 + self.openDataButton.setToolTip(
146 + "편집할 비디오와 관련된 데이터 파일을 선택하고 엽니다. csv 파일을 받습니다. 데이터 파일로 승률데이터(스포츠 경기 영상에 해당할 경우), 유튜브 댓글 데이터, 임의로 작성한 수치 데이터를 사용 가능합니다. \n*임의로 작성한 데이터는 시계열 데이터여야 합니다.\n*데이터가 필수적이지는 않습니다. 데이터를 넣지 않아도 두가지 기능(Check on Timeline, Export)을 이용할 수 있습니다.")
147 + self.openDataButton.setObjectName("openDataButton")
148 + self.horizontalLayout_4b.addWidget(self.openDataButton)
149 + self.checkOnTimelineButton = QtWidgets.QPushButton(
150 + self.horizontalLayoutWidget)
151 + self.checkOnTimelineButton.setToolTip(
152 + "입력된 영상의 추천 편집 지점을 Timeline에 표시해줍니다.")
153 + self.checkOnTimelineButton.setObjectName("checkOnTimelineButton")
154 + self.horizontalLayout_4b.addWidget(self.checkOnTimelineButton)
155 + self.exportButton = QtWidgets.QPushButton(self.horizontalLayoutWidget)
156 + self.exportButton.setToolTip(
157 + "추천 편집 지점(Timeline에 체크된 부분)을 모아 하나의 영상으로 내보냅니다.")
158 + self.exportButton.setObjectName("exportButton")
159 + self.horizontalLayout_4b.addWidget(self.exportButton)
160 + self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
161 + self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 60, 781, 411))
162 + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
163 + self.verticalLayout_video = QtWidgets.QVBoxLayout(
164 + self.verticalLayoutWidget)
165 + self.verticalLayout_video.setContentsMargins(0, 0, 0, 0)
166 + self.verticalLayout_video.setObjectName("verticalLayout_video")
167 + self.label = QtWidgets.QLabel(self.verticalLayoutWidget)
168 + self.label.setAlignment(QtCore.Qt.AlignCenter)
169 + self.label.setObjectName("label")
170 + self.verticalLayout_video.addWidget(self.label)
171 + self.verticalLayoutWidget_2 = QtWidgets.QWidget(self.centralwidget)
172 + self.verticalLayoutWidget_2.setGeometry(
173 + QtCore.QRect(10, 530, 781, 131))
174 + self.verticalLayoutWidget_2.setObjectName("verticalLayoutWidget_2")
175 + self.verticalLayout_bottom = QtWidgets.QVBoxLayout(
176 + self.verticalLayoutWidget_2)
177 + self.verticalLayout_bottom.setContentsMargins(0, 0, 0, 0)
178 + self.verticalLayout_bottom.setObjectName("verticalLayout_bottom")
179 + self.tabWidget = QtWidgets.QTabWidget(self.verticalLayoutWidget_2)
180 + self.tabWidget.setObjectName("tabWidget")
181 + self.tab = QtWidgets.QWidget()
182 + self.tab.setToolTip("추천 편집 지점을 나타내는 타임라인입니다.")
183 + self.tab.setObjectName("tab")
184 + self.label_2 = QtWidgets.QLabel(self.tab)
185 + self.label_2.setGeometry(QtCore.QRect(0, 0, 771, 101))
186 + self.label_2.setAlignment(QtCore.Qt.AlignCenter)
187 + self.label_2.setObjectName("label_2")
188 + self.tabWidget.addTab(self.tab, "")
189 + self.tab_2 = QtWidgets.QWidget()
190 + self.tab_2.setToolTip(
191 + "데이터 파일과 영상에서 추출된 데이터를 바탕으로 하이라이트인 정도를 평가하는 그래프를 보여줍니다.")
192 + self.tab_2.setObjectName("tab_2")
193 + self.label_3 = QtWidgets.QLabel(self.tab_2)
194 + self.label_3.setGeometry(QtCore.QRect(0, 0, 771, 101))
195 + self.label_3.setAlignment(QtCore.Qt.AlignCenter)
196 + self.label_3.setObjectName("label_3")
197 + self.tabWidget.addTab(self.tab_2, "")
198 + self.verticalLayout_bottom.addWidget(self.tabWidget)
199 + self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
200 + self.progressBar.setGeometry(QtCore.QRect(10, 700, 781, 20))
201 + self.progressBar.setProperty("value", 24)
202 + self.progressBar.setToolTip(
203 + "두가지 기능(Check on Timeline, Export)의 진행 상황을 나타냅니다.")
204 + self.progressBar.setObjectName("progressBar")
205 + self.horizontalLayoutWidget_2 = QtWidgets.QWidget(self.centralwidget)
206 + self.horizontalLayoutWidget_2.setGeometry(
207 + QtCore.QRect(10, 480, 781, 44))
208 + self.horizontalLayoutWidget_2.setObjectName("horizontalLayoutWidget_2")
209 + self.horizontalLayout_2b = QtWidgets.QHBoxLayout(
210 + self.horizontalLayoutWidget_2)
211 + self.horizontalLayout_2b.setContentsMargins(0, 0, 0, 0)
212 +
213 + self.horizontalLayout_2b.setObjectName("horizontalLayout_2b")
214 + self.startButton = QtWidgets.QPushButton(self.horizontalLayoutWidget_2)
215 + self.startButton.setToolTip("동영상을 재생하는 버튼입니다.")
216 + self.startButton.setObjectName("startButton")
217 + self.horizontalLayout_2b.addWidget(self.startButton)
218 + self.pauseButton = QtWidgets.QPushButton(self.horizontalLayoutWidget_2)
219 + self.pauseButton.setToolTip("동영상을 일시정지하는 버튼입니다.")
220 + self.pauseButton.setObjectName("pauseButton")
221 + self.horizontalLayout_2b.addWidget(self.pauseButton)
222 + self.horizontalSlider = QtWidgets.QSlider(self.centralwidget)
223 + self.horizontalSlider.setGeometry(QtCore.QRect(18, 670, 762, 22))
224 + self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal)
225 + self.horizontalSlider.setToolTip(
226 + "동영상 재생 지점을 선택할 수 있습니다. Timeline에 체크된 부분을 확인할 때 쓰일 것입니다")
227 + self.horizontalSlider.setObjectName("horizontalSlider")
228 + MainWindow.setCentralWidget(self.centralwidget)
229 + self.menubar = QtWidgets.QMenuBar(MainWindow)
230 + self.menubar.setGeometry(QtCore.QRect(0, 0, 801, 21))
231 + self.menubar.setObjectName("menubar")
232 + self.menuFile = QtWidgets.QMenu(self.menubar)
233 + self.menuFile.setToolTip(
234 + "New Project: 현재 올려진 동영상과 타임라인을 지우고 초기화 시킵니다.\nSettings: 기타 설정을 변경할 수 있습니다. Check on Timeline의 민감도 설정을 할 수 있습니다. \nExit: 프로그램을 종료합니다.")
235 + self.menuFile.setObjectName("menuFile")
236 + self.menuTools = QtWidgets.QMenu(self.menubar)
237 + self.menuTools.setToolTip(
238 + "Add YouTube Link: 편집할 영상이 유튜브에 업로드 되어 있다면, 그 영상의 URL을 입력하여 댓글 데이터를 자동으로 가져옵니다.")
239 + self.menuTools.setObjectName("menuTools")
240 + self.menuHelp = QtWidgets.QMenu(self.menubar)
241 + self.menuHelp.setToolTip("프로그램 사용 가이드와 제작정보")
242 + self.menuHelp.setObjectName("menuHelp")
243 + MainWindow.setMenuBar(self.menubar)
244 + self.statusbar = QtWidgets.QStatusBar(MainWindow)
245 + self.statusbar.setObjectName("statusbar")
246 + MainWindow.setStatusBar(self.statusbar)
247 + self.actionnew_project = QtWidgets.QAction(MainWindow)
248 + self.actionnew_project.setObjectName("actionnew_project")
249 + self.actionSetting = QtWidgets.QAction(MainWindow)
250 + self.actionSetting.setObjectName("actionSetting")
251 + self.actionExit = QtWidgets.QAction(MainWindow)
252 + self.actionExit.setObjectName("actionExit")
253 + self.actionHow_to_use = QtWidgets.QAction(MainWindow)
254 + self.actionHow_to_use.setObjectName("actionHow_to_use")
255 + self.actionAbout = QtWidgets.QAction(MainWindow)
256 + self.actionAbout.setObjectName("actionAbout")
257 + self.actionData = QtWidgets.QAction(MainWindow)
258 + self.actionData.setObjectName("actionData")
259 + self.menuFile.addAction(self.actionnew_project)
260 + self.menuFile.addAction(self.actionSetting)
261 + self.menuFile.addAction(self.actionExit)
262 + self.menuTools.addAction(self.actionData)
263 + self.menuHelp.addAction(self.actionHow_to_use)
264 + self.menuHelp.addAction(self.actionAbout)
265 + self.menubar.addAction(self.menuFile.menuAction())
266 + self.menubar.addAction(self.menuTools.menuAction())
267 + self.menubar.addAction(self.menuHelp.menuAction())
268 +
269 + self.retranslateUi(MainWindow)
270 + self.tabWidget.setCurrentIndex(1)
271 + QtCore.QMetaObject.connectSlotsByName(MainWindow)
272 +
273 + def retranslateUi(self, MainWindow):
274 + _translate = QtCore.QCoreApplication.translate
275 + MainWindow.setWindowTitle(_translate("MainWindow", "Auto Video Editor"))
276 + self.openVideoButton.setText(_translate("MainWindow", "Open Video"))
277 + self.openDataButton.setText(_translate("MainWindow", "Open Data File"))
278 + self.checkOnTimelineButton.setText(
279 + _translate("MainWindow", "Check on Timeline"))
280 + self.exportButton.setText(_translate("MainWindow", "Export"))
281 + self.label.setText(_translate("MainWindow", "video"))
282 + self.label_2.setText(_translate(
283 + "MainWindow", "Timeline: 추천 편집 지점을 나타내는 타임라인입니다."))
284 + self.tabWidget.setTabText(self.tabWidget.indexOf(
285 + self.tab), _translate("MainWindow", "Timeline"))
286 + self.label_3.setText(_translate(
287 + "MainWindow", "Data: 데이터 파일과 영상에서 추출된 데이터를 바탕으로 하이라이트인 정도를 평가하는 그래프를 보여줍니다."))
288 + self.tabWidget.setTabText(self.tabWidget.indexOf(
289 + self.tab_2), _translate("MainWindow", "Data"))
290 + self.startButton.setText(_translate("MainWindow", "Start"))
291 + self.pauseButton.setText(_translate("MainWindow", "Pause"))
292 + self.menuFile.setTitle(_translate("MainWindow", "File"))
293 + self.menuTools.setTitle(_translate("MainWindow", "Tools"))
294 + self.menuHelp.setTitle(_translate("MainWindow", "Help"))
295 + self.actionnew_project.setText(_translate("MainWindow", "New Project"))
296 + self.actionSetting.setText(_translate("MainWindow", "Settings"))
297 + self.actionExit.setText(_translate("MainWindow", "Exit"))
298 + self.actionHow_to_use.setText(_translate("MainWindow", "How to use"))
299 + self.actionAbout.setText(_translate("MainWindow", "About"))
300 + self.actionData.setText(_translate("MainWindow", "XML action"))
301 +
302 +
303 +class WindowClass(QMainWindow, Ui_MainWindow): # Ui_MainWindow를 받음
304 + resized = QtCore.pyqtSignal()
305 +
306 + def __init__(self):
307 + super().__init__()
308 + self.setupUi(self)
309 + self.openVideoButton.clicked.connect(self.OVBtnClicked)
310 + self.startButton.clicked.connect(self.startButtonClicked)
311 + self.pauseButton.clicked.connect(self.pauseButtonclicked)
312 + self.horizontalSlider.sliderPressed.connect(self.horizontalSliderPressed)
313 + self.horizontalSlider.sliderReleased.connect(self.horizontalSliderReleased)
314 + self.horizontalSlider.valueChanged.connect(self.slider_value_changed)
315 + self.openDataButton.clicked.connect(self.ODBtnClicked)
316 + self.checkOnTimelineButton.clicked.connect(self.CTLBtnCLicked)
317 + self.exportButton.clicked.connect(self.EPBtnClicked)
318 +
319 + action = QAction("&Add YouTube link", self)
320 + self.menuTools.addAction(action)
321 + action.triggered.connect(self.AYBtnClicked)
322 +
323 + #버튼 설정
324 + self.startButton.setEnabled(False)
325 + self.pauseButton.setEnabled(False)
326 + self.horizontalSlider.setEnabled(False)
327 + self.openDataButton.setEnabled(False)
328 + self.checkOnTimelineButton.setEnabled(False)
329 + self.exportButton.setEnabled(False)
330 +
331 + self.progressBar.setValue(0)
332 +
333 + #프레임 정보
334 + self.frameID = 0 #현재 프레임
335 + self.stframe = 0 #시작 프레임
336 + self.endframe = 0 #끝 프레임
337 + self.fps = 0 #초당 프레임
338 +
339 + #영상 크기 조절
340 + self.scaleFactor = 0.5
341 +
342 + # thread
343 + self.wthread = workerThread(self)
344 + self.wthread.updatedM.connect(self.horizontalSliderSet)
345 + self.wthread.start()
346 +
347 + #기타 멤버 변수들
348 + self.limg = np.zeros((1, 1, 1))
349 + self.isbusy = 0
350 + self.frameHeight = 1
351 + self.frameWidth = 1
352 + self.isRun = True
353 + self.resizegoing = False
354 + self.sliderbusy = False
355 + self.sliderbusy2 = False
356 + self.linebusy = False
357 + self.cap = None
358 + self.timer = None
359 + self.isvideo = False
360 + self.videoFileName = ""
361 + self.isthreadActive = False
362 +
363 + #데이터
364 + self.clip = 0
365 + self.max_slice = 5
366 + self.threshold = 0.4
367 + self.inputData = pd.DataFrame()
368 + self.audioData = pd.DataFrame()
369 + self.commentsData = 0
370 + self.highlightData = pd.DataFrame()
371 + self.isdata = False
372 + self.iscomments = False
373 + self.videoID = ""
374 +
375 + #그리기
376 + layout1 = QVBoxLayout()
377 + layout2 = QVBoxLayout()
378 + self.figure1 = Figure()
379 + self.figure2 = Figure()
380 +
381 + self.label_2.canvas = FigureCanvas(self.figure1)
382 + self.label_3.canvas = FigureCanvas(self.figure2)
383 +
384 + layout1.addWidget(self.label_2.canvas)
385 + layout2.addWidget(self.label_3.canvas)
386 + self.label_2.setLayout(layout1)
387 + self.label_3.setLayout(layout2)
388 +
389 + self.ax1 = self.figure1.add_subplot(1, 1, 1)
390 + self.ax2 = self.figure2.add_subplot(1, 1, 1)
391 +
392 + self.ax1.get_yaxis().set_visible(False)
393 + self.ax1.get_xaxis().set_visible(True)
394 +
395 + self.figure1.subplots_adjust(left=0.001, right=0.999, top=1.0, bottom=0.1)
396 + self.figure2.subplots_adjust(left=0.001, right=0.999, top=1.0, bottom=0.1)
397 +
398 + def OVBtnClicked(self):
399 + self.isthreadActive = False
400 + fileName = QFileDialog.getOpenFileName(
401 + None, caption="Select Video File", directory=QtCore.QDir.currentPath())
402 + if len(fileName[0]) > 0:
403 + self.videoFileName = fileName[0]
404 + self.cap = cv2.VideoCapture(fileName[0]) # 비디오 캡쳐하는 부분
405 + self.isvideo = True
406 + else:
407 + return
408 +
409 + length = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
410 + print("length: " + str(length))
411 + self.fps = self.cap.get(cv2.CAP_PROP_FPS)
412 + print("fps: " + str(self.fps))
413 +
414 + self.stframe = 0
415 + self.endframe = length
416 + #self.stframe = int(self.data.values[1, 1] * self.fps)
417 + #print("stframe: " + str(self.stframe))
418 + #self.endframe = int(self.data.values[-1, 1] * self.fps)
419 + #print("endframe: " + str(self.endframe))
420 + #self.ui.horizontalSlider.setMaximum(self.endframe - self.stframe)
421 +
422 + #self.cap.set(1, self.stframe)
423 + #self.cap.set(1, 200)
424 + ret, frame = self.cap.read()
425 + self.drawmin = 1
426 + self.frameID = self.stframe
427 + self.limg = frame
428 + self.frameHeight = frame.shape[0]
429 + self.frameWidth = frame.shape[1]
430 +
431 + nchannel = frame.shape[2]
432 + limg2 = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
433 +
434 + timg = cv2.resize(limg2, (int(
435 + self.scaleFactor * limg2.shape[1]), int(self.scaleFactor * limg2.shape[0])))
436 + limage = QtGui.QImage(timg.data, timg.shape[1], timg.shape[0], nchannel * timg.shape[1],
437 + QtGui.QImage.Format_RGB888)
438 +
439 + self.label.setPixmap(QtGui.QPixmap(limage))
440 + self.startButton.setEnabled(True)
441 + self.horizontalSlider.setEnabled(True)
442 + self.openDataButton.setEnabled(True)
443 + self.checkOnTimelineButton.setEnabled(True)
444 + self.exportButton.setEnabled(False)
445 + self.horizontalSlider.setMaximum(self.endframe - self.stframe)
446 + self.horizontalSlider.setValue(self.stframe)
447 + self.isdata = False
448 +
449 + def updateFrame(self):
450 + self.frameID += 1
451 +
452 + def startButtonClicked(self):
453 +
454 + if self.isthreadActive:
455 + return
456 +
457 + self.startButton.setEnabled(False)
458 +
459 + self.timer = perpetualTimer(1.0 / self.fps, self.updateFrame)
460 +
461 + self.timer.start()
462 +
463 + self.pauseButton.setEnabled(True)
464 + self.isthreadActive = True
465 +
466 + print("thread activatied")
467 +
468 + def pauseButtonclicked(self):
469 +
470 + if not self.isthreadActive:
471 + return
472 +
473 + self.startButton.setEnabled(True)
474 + self.pauseButton.setEnabled(False)
475 + if not self.timer is None:
476 + self.timer.cancel()
477 + self.isthreadActive = False
478 +
479 + def videoSec(self):
480 + return int(self.frameID / self.fps)
481 +
482 + def horizontalSliderSet(self, cnt): #슬라이더 위치 업데이트
483 + if cnt + 1 - self.stframe > self.horizontalSlider.maximum() or self.sliderbusy or self.resizegoing:
484 + return
485 + self.sliderbusy2 = True
486 + self.horizontalSlider.setValue(cnt + 1 - self.stframe)
487 +
488 + tsec = cnt / self.fps
489 + tmin = int(tsec / 60)
490 + ttsec = int(tsec - 60 * tmin)
491 + ksec = tsec - 60 * tmin - ttsec
492 +
493 + self.statusbar.showMessage(
494 + "Time: " + str(tmin).zfill(2) + ":" + str(ttsec).zfill(2) + ":" + str(int(ksec * 100)))
495 + self.sliderbusy2 = False
496 + """
497 + def lineSliderSet(self, cnt): #그래프상 수직선 위치 업데이트
498 + if cnt + 1 - self.stframe > self.horizontalSlider.maximum():
499 + return
500 +
501 + self.linebusy = True
502 + pdraw = self.drawmin
503 +
504 + self.drawmin -= 20
505 + if self.drawmin < 1:
506 + self.drawmin = 1
507 + while (cnt + 1) / self.fps > self.data.values[self.drawmin, 1]:
508 + self.drawmin += 1
509 + if not self.drawmin == pdraw or pdraw == 1:
510 + wr = ref(self.ax1.lines[5])
511 + self.ax1.lines.remove(wr())
512 + self.ui.bottomImage.canvas.draw()
513 + self.ax1.plot([self.data.values[self.drawmin, 1], self.data.values[self.drawmin, 1]], [0, 3.4], 'k',
514 + linewidth=2.0)
515 + self.ui.bottomImage.canvas.draw()
516 +
517 + tsec = cnt / self.fps
518 + tmin = int(tsec / 60)
519 + ttsec = int(tsec - 60 * tmin)
520 + ksec = tsec - 60 * tmin - ttsec
521 +
522 + self.ui.statusbar.showMessage(
523 + "Frame Time: " + str(tmin).zfill(2) + ":" + str(ttsec).zfill(2) + ":" + str(int(ksec * 100)))
524 +
525 + self.linebusy = False
526 + """
527 +
528 + def horizontalSliderPressed(self):
529 + self.sliderbusy = True
530 +
531 + def slider_value_changed(self):
532 + #print('slidervalue change')
533 + self.horizontalSliderIncrease(0)
534 + #if not self.isthreadActive:
535 + # print("thread is not active")
536 + # self.horizontalSliderIncrease(0)
537 +
538 +
539 + def horizontalSliderIncrease(self, val):
540 + if self.sliderbusy or self.resizegoing:
541 + return
542 + self.sliderbusy = True
543 + # print(self.horizontalSlider.value())
544 + # self.ui.horizontalSlider.setValue(self.ui.horizontalSlider.value()+val)
545 + # print(self.frameID)
546 + self.frameID = self.stframe + self.horizontalSlider.value() -1
547 + # print(self.frameID)
548 + # self.drawmin=1
549 + if self.startButton.isEnabled():
550 + self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frameID)
551 + ret, frame = self.cap.read()
552 + self.limg = frame
553 + # self.on_zoomfit_clicked()
554 + nchannel = frame.shape[2]
555 + limg2 = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
556 + timg = cv2.resize(limg2, (int(self.scaleFactor * limg2.shape[1]), int(self.scaleFactor * limg2.shape[0])))
557 + limage = QtGui.QImage(timg.data, timg.shape[1], timg.shape[0], nchannel * timg.shape[1],
558 + QtGui.QImage.Format_RGB888)
559 + self.label.setPixmap(QtGui.QPixmap(limage))
560 + #self.lineSliderSet(self.frameID)
561 + self.sliderbusy = False
562 +
563 + def horizontalSliderReleased(self):
564 + self.frameID = self.stframe + self.horizontalSlider.value()-1
565 + print(self.frameID)
566 + print(self.horizontalSlider.value())
567 +
568 + self.drawmin = 1
569 + if self.startButton.isEnabled():
570 + self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frameID)
571 + ret, frame = self.cap.read()
572 + self.limg = frame
573 + #self.on_zoomfit_clicked()
574 + nchannel = frame.shape[2]
575 + limg2 = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
576 + timg = cv2.resize(limg2, (int(self.scaleFactor * limg2.shape[1]), int(self.scaleFactor * limg2.shape[0])))
577 + limage = QtGui.QImage(timg.data, timg.shape[1], timg.shape[0], nchannel * timg.shape[1],
578 + QtGui.QImage.Format_RGB888)
579 + self.label.setPixmap(QtGui.QPixmap(limage))
580 + #self.lineSliderSet(self.frameID)
581 +
582 + self.sliderbusy = False
583 +
584 + def ODBtnClicked(self):
585 + self.pauseButtonclicked()
586 + fileName = QFileDialog.getOpenFileName(
587 + None, caption="Select Data File(only csv)", directory=QtCore.QDir.currentPath())
588 + if len(fileName[0]) > 0:
589 + data = pd.read_csv(fileName[0])
590 + self.inputData = data
591 + self.isdata = True
592 +
593 + #print(self.inputData)
594 +
595 +
596 + return
597 +
598 + def CTLBtnCLicked(self):
599 + #데이터 가져오기
600 + self.pauseButtonclicked()
601 + choice = QMessageBox.question(self, '', '기본 설정값을 사용하겠습니까?', QMessageBox.Yes|QMessageBox.No)
602 + if choice == QMessageBox.No:
603 + input_max_slice, ok = QInputDialog.getInt(self, '단위 구간 설정', '하이라이트성을 평가할 구간의 길이를 설정하세요. (초)')
604 + if ok:
605 + if input_max_slice >= 0:
606 + self.max_slice = input_max_slice
607 + else:
608 + return
609 +
610 + input_threshold, ok = QInputDialog.getDouble(self, '임계값 설정', '하이라이트 포함 임계값을 설정하세요. (0~1 실수)')
611 + if ok:
612 + if 0 <= input_threshold <= 1:
613 + self.threshold = input_threshold
614 + else:
615 + return
616 + else:
617 + self.max_slice = 5
618 + self.threshold = 0.4
619 +
620 + self.clip = Clip(self.videoFileName)
621 + self.audioData = self.clip.getLoudSection(max_slice=self.max_slice, threshold=400)
622 + self.drawData(max_slice=self.max_slice, threshold=self.threshold)
623 + self.exportButton.setVisible(True)
624 + self.exportButton.setEnabled(True)
625 +
626 + return
627 +
628 + #영상 내보내기
629 + def EPBtnClicked(self):
630 + #print(self.highlightData)
631 + self.clip.concatVideo(self.highlightData, self.max_slice)
632 + return
633 +
634 + #타임라인 그리기
635 + #def drawTimeline(self):
636 + # return
637 +
638 +
639 + #데이터 그리기
640 + def decisionFunction(self, audioData, inputData = None):
641 + data = pd.DataFrame()
642 + #normalization(min-max scaling)
643 + audioData["energy"] = (audioData["energy"] - audioData["energy"].min()) / (audioData["energy"].max() - audioData["energy"].min())
644 +
645 + if inputData is None:
646 + data = audioData
647 + else:
648 + self.inputData.iloc[:, 0] = (self.inputData.iloc[:, 0] - self.inputData.iloc[:, 0].min()) / (
649 + self.inputData.iloc[:, 0].max() - self.inputData.iloc[:, 0].min())
650 + self.inputData = self.inputData.reindex(self.inputData.index.repeat(int(60 / self.max_slice))).assign()
651 + self.inputData = self.inputData.reset_index()
652 + #print(self.inputData)
653 + data = 0.9*audioData.iloc[:,0] + 0.1*self.inputData.iloc[:, 1]
654 + data = data.to_frame()
655 + data = data.iloc[:len(audioData), 0]
656 + data = data.to_frame()
657 + data["start"] = audioData["start"].tolist()
658 +
659 + #result3 = pd.concat([df1, df2], axis=1)
660 + #data.concat([data, df], axis=1)
661 +
662 + return data
663 +
664 + #timeline, data 그리기
665 + def drawData(self, max_slice=10, threshold=0.4):
666 + if self.isdata is False and self.isvideo is True:
667 + data = self.decisionFunction(self.audioData) #그릴 데이터
668 + elif self.isdata is True and self.isvideo is True:
669 + data = self.decisionFunction(self.audioData, self.inputData) #그릴 데이터
670 + else:
671 + return
672 +
673 + #ax2(Data)그리기
674 + self.ax2.clear()
675 + self.ax2.set_ylim([0, 1.1])
676 + #print(data)
677 + ndata = data.to_numpy()[:, 0]
678 + print(ndata)
679 +
680 + stretchedata = np.zeros(int(self.endframe/self.fps)+1) #영상의 실제 끝으로설정
681 + #print(stretchedata[10])
682 + #print("check")
683 + for i in range(len(stretchedata)):
684 + stretchedata[i] = ndata[int(i/max_slice)]
685 +
686 + #print(stretchedata)
687 + self.ax2.plot(stretchedata, '-c')
688 +
689 + #self.ax2.hist(data.to_numpy()[:,0], 10, histtype='stepfilled')
690 + self.ax2.set_xlim((self.stframe/self.fps), int(self.endframe/self.fps))
691 +
692 + #print(data.to_numpy()[:,0])
693 + #self.highlightData = data.to_numpy()[:,0]
694 +
695 +
696 + self.ax2.plot([data.to_numpy()[0, 1], data.to_numpy()[0, 1]], [0, 1.1], 'k', linewidth=2.0)
697 +
698 + #self.ax1.plot([self.data.values[1, 1], self.data.values[1, 1]], [0, 3.4], 'k', linewidth=2.0)
699 + #print(data.to_numpy()[1, 1], data.to_numpy()[-1, 1])
700 + self.ax2.xaxis.set_ticks(np.arange(self.stframe/self.fps, self.endframe/self.fps , max_slice))
701 +
702 + self.ax2.xaxis.set_visible(True)
703 + self.label_3.canvas.draw()
704 +
705 + #ax1(Timeline)그리기
706 + self.ax1.clear()
707 + self.ax1.set_ylim([0.5, 1.5])
708 + self.ax1.set_xlim((self.stframe / self.fps), int(self.endframe / self.fps))
709 +
710 + timelinedata = np.zeros(int(self.endframe/self.fps)+1)
711 + for i in range(len(timelinedata)):
712 + if stretchedata[i] > threshold:
713 + timelinedata[i] = 1
714 +
715 + if self.iscomments is True:
716 + os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
717 + service = get_authenticated_service()
718 + comments = get_video_comments(
719 + service, part='snippet', videoId=self.videoID, textFormat='plainText')
720 + video_duration = get_video_duration(
721 + service, part='contentDetails', id=self.videoID)
722 + # print(video_duration)
723 + # print(comments)
724 + self.commentsData = make_video_timeline(comments, video_duration, self.max_slice)
725 + print(str(len(timelinedata)) + "," + str(len(self.commentsData)))
726 + if not (-1 <= len(timelinedata)/self.max_slice - len(self.commentsData) <= 1):
727 + choice = QMessageBox.warning(self, 'warning', '유튜브 댓글 데이터가 올바르지 않습니다. 제외하고 진행하겠습니까?', QMessageBox.Yes | QMessageBox.No)
728 + if choice == QMessageBox.Yes:
729 + self.highlightData = timelinedata
730 + self.iscomments = False
731 + else:
732 + sys.exit()
733 + else: #commentsData반영
734 + for i in range(len(timelinedata)):
735 + #print(i)
736 + if timelinedata[i] == 0:
737 + #print(i/self.max_slice)
738 + if self.commentsData[int(i/self.max_slice)] == 1:
739 + timelinedata[i] = 1
740 +
741 + print(self.commentsData)
742 + self.highlightData = timelinedata
743 +
744 + self.ax1.plot(timelinedata, '*y')
745 + self.ax1.set_xlim((self.stframe / self.fps), int(self.endframe / self.fps))
746 + self.ax1.xaxis.set_ticks(np.arange(self.stframe / self.fps, self.endframe / self.fps, max_slice))
747 + self.ax1.plot([data.to_numpy()[0, 1], data.to_numpy()[0, 1]], [0.5, 1.5], 'k', linewidth=2.0)
748 + self.label_2.canvas.draw()
749 +
750 + #스케일링 방법 바꾸기
751 + return
752 +
753 + def AYBtnClicked(self):
754 + video_id, ok = QInputDialog.getText(self, '영상 ID', '유튜브 영상 ID를 입력하세요. (/watch?v=~)')
755 + if not ok:
756 + return
757 + self.videoID = video_id
758 + self.iscomments = True
759 +
760 +
761 +
762 +
763 +#check on timeline and export
764 +import moviepy.editor as mp
765 +import librosa
766 +
767 +
768 +class Clip:
769 +
770 + def __init__(self, fileName):
771 +
772 + self.clip = mp.VideoFileClip(fileName)
773 + self.fullFileName = fileName
774 + self.fileName = fileName[:fileName.rfind('.')]
775 +
776 + def getLoudSection(self, max_slice=10, threshold=180): #max_slice: 몇 초 단위로 나눌 것인지 결정
777 + self.clip.audio.write_audiofile(self.fileName+'.wav')
778 + x, sr = librosa.load(self.fileName + '.wav', sr=16000) #x: 오디오 파일을 나눈(1초에 sr개로 나눔) 샘플들의 진폭이 저장됨, 샘플의 개수는 sr*영상길이
779 + #print(x)
780 + #print(len(x))
781 + window_length = max_slice * sr #한 구간에 해당하는 샘플의 개수
782 + energy = np.array([sum(abs(x[i: i + window_length] ** 2)) #windwo_length구간 안에있는 모든 샘플들의 진폭의 제곱의 합->소리의 크기
783 + for i in range(0, len(x), window_length)]) #len(x)는 sr*영상길이인데 이것을 window_length만큼 끊음 i = 0, winlength, winlength*2, winlength*3, ...
784 + #print(energy)
785 + #energy data를 dataframe으로 만듦
786 + df = pd.DataFrame(columns=['energy', 'start', 'end'])
787 + row_index = 0
788 + for i in range(len(energy)):
789 + value = energy[i]
790 + i = np.where(energy == value)[0]
791 + df.loc[row_index, 'energy'] = value
792 + df.loc[row_index, 'start'] = i[0] * max_slice
793 + df.loc[row_index, 'end'] = (i[0] + 1) * max_slice
794 + row_index = row_index + 1
795 + #print(df)
796 + """
797 + if value >= threshold:
798 + i = np.where(energy == value)[0]
799 + df.loc[row_index, 'energy'] = value
800 + df.loc[row_index, 'start'] = i[0] * max_slice
801 + df.loc[row_index, 'end'] = (i[0] + 1) * max_slice
802 + row_index = row_index + 1
803 + print(df) #threshold를 넘은 구간이 저장됨
804 + """
805 + #연속구간으로 나타내기
806 + """
807 + temp = []
808 + i = 0
809 + j = 0
810 + n = len(df) - 2
811 + m = len(df) - 1
812 + while (i <= n):
813 + j = i + 1
814 + while (j <= m):
815 + if (df['end'][i] == df['start'][j]):
816 + df.loc[i, 'end'] = df.loc[j, 'end']
817 + temp.append(j)
818 + j = j + 1
819 + else:
820 + i = j
821 + break
822 + df.drop(temp, axis=0, inplace=True)
823 + print(df)
824 + """
825 + return df
826 +
827 +
828 + def concatVideo(self, data, max_slice):
829 + i = 0
830 + clips = []
831 + final1 = []
832 + final2 = []
833 +
834 + while i < len(data):
835 + if data[i] == 1:
836 + subclip = self.clip.subclip(i, i+max_slice)
837 + clips.append(subclip)
838 + i += max_slice
839 +
840 + n= 5
841 + result = [clips[i * n:(i + 1) * n] for i in range((len(clips) + n - 1) // n)]
842 + print(len(result))
843 + for j in range(len(result)):
844 + final1.append(mp.concatenate_videoclips(result[j]))
845 + final2 = mp.concatenate_videoclips(final1)
846 + final2.write_videofile(self.fileName+'_highlights'+'.mp4')
847 +
848 +import pickle
849 +import csv
850 +import os
851 +import pandas as pd
852 +import re
853 +import math
854 +
855 +import google.oauth2.credentials
856 +
857 +from googleapiclient.discovery import build
858 +from googleapiclient.errors import HttpError
859 +from google_auth_oauthlib.flow import InstalledAppFlow
860 +from google.auth.transport.requests import Request
861 +
862 +# parameter 지정
863 +CLIENT_SECRETS_FILE = "/Users/ASUS/Desktop/work/3-1/디자인적사고/project/client_secret.json"
864 +CLIENT_SECRETS_FILE = "/Users/user/Documents/카카오톡 받은 파일/api/client_secret.json"
865 +SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl']
866 +API_SERVICE_NAME = 'youtube'
867 +API_VERSION = 'v3'
868 +
869 +# 인증 모듈
870 +
871 +
872 +def get_authenticated_service():
873 + credentials = None
874 + if os.path.exists('token.pickle'):
875 + with open('token.pickle', 'rb') as token:
876 + credentials = pickle.load(token)
877 + # Check if the credentials are invalid or do not exist
878 + if not credentials or not credentials.valid:
879 + # Check if the credentials have expired
880 + if credentials and credentials.expired and credentials.refresh_token:
881 + credentials.refresh(Request())
882 + else:
883 + flow = InstalledAppFlow.from_client_secrets_file(
884 + CLIENT_SECRETS_FILE, SCOPES)
885 + credentials = flow.run_console()
886 +
887 + # Save the credentials for the next run
888 + with open('token.pickle', 'wb') as token:
889 + pickle.dump(credentials, token)
890 +
891 + return build(API_SERVICE_NAME, API_VERSION, credentials=credentials)
892 +
893 +# video comment 데이터프레임화
894 +
895 +
896 +def get_video_comments(service, **kwargs):
897 + results = service.commentThreads().list(**kwargs).execute()
898 + youtube_pd = pd.DataFrame()
899 + # filename = input('.csv 형태로 입력하세요')
900 + while results:
901 + for item in results['items']:
902 + comment1 = item['snippet']['topLevelComment']['snippet']['textDisplay']
903 + # comment2 = item['snippet']['topLevelComment']['snippet']['publishedAt']
904 + # comment3 = item['snippet']['topLevelComment']['snippet']['authorDisplayName']
905 + # comment2 = item['snippet']['topLevelComment']['snippet']['viewerRating']
906 + comment2 = item['snippet']['topLevelComment']['snippet']['likeCount']
907 +
908 + pd_data = {"comment": comment1, "likeCount": comment2}
909 +
910 + youtube_pd = youtube_pd.append(pd_data, ignore_index=True)
911 + # youtube_pd.to_csv(filename, index=False, encoding='utf-8-sig')
912 + # Check if another page exists
913 + if 'nextPageToken' in results:
914 + kwargs['pageToken'] = results['nextPageToken']
915 + results = service.commentThreads().list(**kwargs).execute()
916 + else:
917 + break
918 +
919 + return youtube_pd
920 +
921 +# 코멘트에서 타임라인만 추출
922 +
923 +
924 +def make_video_timeline(commentFrame, duration, max_slice):
925 + youtube_pd_sorted_by_likeCount = commentFrame.sort_values(by='likeCount')
926 + # .str.split(" ") # .values.tolist()
927 + #youtube_pd_sorted_by_likeCount = youtube_pd_sorted_by_likeCount.head(30) # 갯수 정해서 추출하기
928 + comment = youtube_pd_sorted_by_likeCount['comment']
929 + comment_split = comment.str.split(" ").values.tolist()
930 + result = []
931 + for i in range(len(comment_split)):
932 + for j in range(len(comment_split[i])):
933 + if ":" in comment_split[i][j]:
934 + result.append(comment_split[i][j])
935 + hi = ' '.join(result)
936 + timeline = re.findall(r'(\d{1,2}):(\d{2})', hi)
937 + timelist = []
938 + for groups in timeline:
939 + timelist.append(int(groups[0])*60+int(groups[1]))
940 + timelist.sort()
941 + # print(timelist)
942 + timecheck = []
943 + for i in range(math.ceil(duration/max_slice)):
944 + timecheck.append(0)
945 + for i in range(math.ceil(duration/max_slice)):
946 + for j in timelist:
947 + if i <= int(int(j)/max_slice) <= (i+10/max_slice):
948 + timecheck[i] = 1
949 + # print(timecheck)
950 + return timecheck
951 +
952 +# 비디오 총 길이 추출
953 +
954 +
955 +def get_video_duration(service, **kwargs):
956 + results = service.videos().list(**kwargs).execute()
957 + comment_data = results['items']
958 + comment_data = comment_data[0]['contentDetails']['duration']
959 + match = re.match(r'PT(\d+H)?(\d+M)?(\d+S)?', comment_data).groups()
960 + hours = _js_parseInt(match[0]) if match[0] else 0
961 + minutes = _js_parseInt(match[1]) if match[1] else 0
962 + seconds = _js_parseInt(match[2]) if match[2] else 0
963 + return hours * 3600 + minutes * 60 + seconds
964 +
965 +# 문자열에서 숫자만 추출
966 +
967 +
968 +def _js_parseInt(string):
969 + return int(''.join([x for x in string if x.isdigit()]))
970 +
971 +"""
972 +if __name__ == '__main__':
973 + # When running locally, disable OAuthlib's HTTPs verification. When
974 + # running in production *do not* leave this option enabled.
975 + os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
976 + service = get_authenticated_service()
977 + video_id = input('Enter a video_id: ')
978 + comments = get_video_comments(
979 + service, part='snippet', videoId=video_id, textFormat='plainText')
980 + video_duration = get_video_duration(
981 + service, part='contentDetails', id=video_id)
982 + # print(video_duration)
983 + # print(comments)
984 + commentdata = make_video_timeline(comments, video_duration)
985 + print(commentdata)
986 +"""
987 +
988 +if __name__ == "__main__":
989 + app = QApplication(sys.argv)
990 + myWindow = WindowClass()
991 + myWindow.show()
992 + sys.exit(app.exec_())
993 +