유희평

Heepyung

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 + self.ax1.clear()
449 + self.ax2.clear()
450 +
451 + def updateFrame(self):
452 + self.frameID += 1
453 +
454 + def startButtonClicked(self):
455 +
456 + if self.isthreadActive:
457 + return
458 +
459 + self.startButton.setEnabled(False)
460 +
461 + self.timer = perpetualTimer(1.0 / self.fps, self.updateFrame)
462 +
463 + self.timer.start()
464 +
465 + self.pauseButton.setEnabled(True)
466 + self.isthreadActive = True
467 +
468 + print("thread activatied")
469 +
470 + def pauseButtonclicked(self):
471 +
472 + if not self.isthreadActive:
473 + return
474 +
475 + self.startButton.setEnabled(True)
476 + self.pauseButton.setEnabled(False)
477 + if not self.timer is None:
478 + self.timer.cancel()
479 + self.isthreadActive = False
480 +
481 + def videoSec(self):
482 + return int(self.frameID / self.fps)
483 +
484 + def horizontalSliderSet(self, cnt): #슬라이더 위치 업데이트
485 + if cnt + 1 - self.stframe > self.horizontalSlider.maximum() or self.sliderbusy or self.resizegoing:
486 + return
487 + self.sliderbusy2 = True
488 + self.horizontalSlider.setValue(cnt + 1 - self.stframe)
489 +
490 + tsec = cnt / self.fps
491 + tmin = int(tsec / 60)
492 + ttsec = int(tsec - 60 * tmin)
493 + ksec = tsec - 60 * tmin - ttsec
494 +
495 + self.statusbar.showMessage(
496 + "Time: " + str(tmin).zfill(2) + ":" + str(ttsec).zfill(2) + ":" + str(int(ksec * 100)))
497 + self.sliderbusy2 = False
498 +
499 + def horizontalSliderPressed(self):
500 + self.sliderbusy = True
501 +
502 + def slider_value_changed(self):
503 + #print('slidervalue change')
504 + self.horizontalSliderIncrease(0)
505 + #if not self.isthreadActive:
506 + # print("thread is not active")
507 + # self.horizontalSliderIncrease(0)
508 +
509 +
510 + def horizontalSliderIncrease(self, val):
511 + if self.sliderbusy or self.resizegoing:
512 + return
513 + self.sliderbusy = True
514 + # print(self.horizontalSlider.value())
515 + # self.ui.horizontalSlider.setValue(self.ui.horizontalSlider.value()+val)
516 + # print(self.frameID)
517 + self.frameID = self.stframe + self.horizontalSlider.value() -1
518 + # print(self.frameID)
519 + # self.drawmin=1
520 + if self.startButton.isEnabled():
521 + self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frameID)
522 + ret, frame = self.cap.read()
523 + self.limg = frame
524 + # self.on_zoomfit_clicked()
525 + nchannel = frame.shape[2]
526 + limg2 = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
527 + timg = cv2.resize(limg2, (int(self.scaleFactor * limg2.shape[1]), int(self.scaleFactor * limg2.shape[0])))
528 + limage = QtGui.QImage(timg.data, timg.shape[1], timg.shape[0], nchannel * timg.shape[1],
529 + QtGui.QImage.Format_RGB888)
530 + self.label.setPixmap(QtGui.QPixmap(limage))
531 + #self.lineSliderSet(self.frameID)
532 + self.sliderbusy = False
533 +
534 + def horizontalSliderReleased(self):
535 + self.frameID = self.stframe + self.horizontalSlider.value()-1
536 + print(self.frameID)
537 + print(self.horizontalSlider.value())
538 +
539 + self.drawmin = 1
540 + if self.startButton.isEnabled():
541 + self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.frameID)
542 + ret, frame = self.cap.read()
543 + self.limg = frame
544 + #self.on_zoomfit_clicked()
545 + nchannel = frame.shape[2]
546 + limg2 = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
547 + timg = cv2.resize(limg2, (int(self.scaleFactor * limg2.shape[1]), int(self.scaleFactor * limg2.shape[0])))
548 + limage = QtGui.QImage(timg.data, timg.shape[1], timg.shape[0], nchannel * timg.shape[1],
549 + QtGui.QImage.Format_RGB888)
550 + self.label.setPixmap(QtGui.QPixmap(limage))
551 + #self.lineSliderSet(self.frameID)
552 +
553 + self.sliderbusy = False
554 +
555 + def ODBtnClicked(self):
556 + self.pauseButtonclicked()
557 + fileName = QFileDialog.getOpenFileName(
558 + None, caption="Select Data File(only csv)", directory=QtCore.QDir.currentPath())
559 + if len(fileName[0]) > 0:
560 + data = pd.read_csv(fileName[0])
561 + self.inputData = data
562 + self.isdata = True
563 +
564 + #print(self.inputData)
565 +
566 +
567 + return
568 +
569 + def CTLBtnCLicked(self):
570 + #데이터 가져오기
571 + self.pauseButtonclicked()
572 + choice = QMessageBox.question(self, '', '기본 설정값을 사용하겠습니까?', QMessageBox.Yes|QMessageBox.No)
573 + if choice == QMessageBox.No:
574 + input_max_slice, ok = QInputDialog.getInt(self, '단위 구간 설정', '하이라이트성을 평가할 구간의 길이를 설정하세요. (초)')
575 + if ok:
576 + if input_max_slice >= 0:
577 + self.max_slice = input_max_slice
578 + else:
579 + return
580 +
581 + input_threshold, ok = QInputDialog.getDouble(self, '임계값 설정', '하이라이트 포함 임계값을 설정하세요. (0~1 실수)')
582 + if ok:
583 + if 0 <= input_threshold <= 1:
584 + self.threshold = input_threshold
585 + else:
586 + return
587 + else:
588 + self.max_slice = 5
589 + self.threshold = 0.4
590 +
591 + self.clip = Clip(self.videoFileName)
592 + self.audioData = self.clip.getLoudSection(max_slice=self.max_slice, threshold=400)
593 + self.drawData(max_slice=self.max_slice, threshold=self.threshold)
594 + self.exportButton.setVisible(True)
595 + self.exportButton.setEnabled(True)
596 +
597 + return
598 +
599 + #영상 내보내기
600 + def EPBtnClicked(self):
601 + #print(self.highlightData)
602 + self.clip.concatVideo(self.highlightData, self.max_slice)
603 + return
604 +
605 + #타임라인 그리기
606 + #def drawTimeline(self):
607 + # return
608 +
609 +
610 + #데이터 그리기
611 + def decisionFunction(self, audioData, inputData = None):
612 + data = pd.DataFrame()
613 + #normalization(min-max scaling)
614 + audioData["energy"] = (audioData["energy"] - audioData["energy"].min()) / (audioData["energy"].max() - audioData["energy"].min())
615 +
616 + if inputData is None:
617 + data = audioData
618 + else:
619 + self.inputData.iloc[:, 0] = (self.inputData.iloc[:, 0] - self.inputData.iloc[:, 0].min()) / (
620 + self.inputData.iloc[:, 0].max() - self.inputData.iloc[:, 0].min())
621 + self.inputData = self.inputData.reindex(self.inputData.index.repeat(int(60 / self.max_slice))).assign()
622 + self.inputData = self.inputData.reset_index()
623 + #print(self.inputData)
624 + data = 0.9*audioData.iloc[:,0] + 0.1*self.inputData.iloc[:, 1]
625 + data = data.to_frame()
626 + data = data.iloc[:len(audioData), 0]
627 + data = data.to_frame()
628 + data["start"] = audioData["start"].tolist()
629 +
630 + #result3 = pd.concat([df1, df2], axis=1)
631 + #data.concat([data, df], axis=1)
632 +
633 + return data
634 +
635 + #timeline, data 그리기
636 + def drawData(self, max_slice=10, threshold=0.4):
637 + if self.isdata is False and self.isvideo is True:
638 + data = self.decisionFunction(self.audioData) #그릴 데이터
639 + elif self.isdata is True and self.isvideo is True:
640 + data = self.decisionFunction(self.audioData, self.inputData) #그릴 데이터
641 + else:
642 + return
643 +
644 + #ax2(Data)그리기
645 + self.ax2.clear()
646 + self.ax2.set_ylim([0, 1.1])
647 + #print(data)
648 + ndata = data.to_numpy()[:, 0]
649 + print(ndata)
650 +
651 + stretchedata = np.zeros(int(self.endframe/self.fps)+1) #영상의 실제 끝으로설정
652 + #print(stretchedata[10])
653 + #print("check")
654 + for i in range(len(stretchedata)):
655 + stretchedata[i] = ndata[int(i/max_slice)]
656 +
657 + #print(stretchedata)
658 + self.ax2.plot(stretchedata, '-c')
659 +
660 + #self.ax2.hist(data.to_numpy()[:,0], 10, histtype='stepfilled')
661 + self.ax2.set_xlim((self.stframe/self.fps), int(self.endframe/self.fps))
662 +
663 + #print(data.to_numpy()[:,0])
664 + #self.highlightData = data.to_numpy()[:,0]
665 +
666 +
667 + self.ax2.plot([data.to_numpy()[0, 1], data.to_numpy()[0, 1]], [0, 1.1], 'k', linewidth=2.0)
668 +
669 + #self.ax1.plot([self.data.values[1, 1], self.data.values[1, 1]], [0, 3.4], 'k', linewidth=2.0)
670 + #print(data.to_numpy()[1, 1], data.to_numpy()[-1, 1])
671 + self.ax2.xaxis.set_ticks(np.arange(self.stframe/self.fps, self.endframe/self.fps , max_slice))
672 +
673 + self.ax2.xaxis.set_visible(True)
674 + self.label_3.canvas.draw()
675 +
676 + #ax1(Timeline)그리기
677 + self.ax1.clear()
678 + self.ax1.set_ylim([0.5, 1.5])
679 + self.ax1.set_xlim((self.stframe / self.fps), int(self.endframe / self.fps))
680 +
681 + timelinedata = np.zeros(int(self.endframe/self.fps)+1)
682 + for i in range(len(timelinedata)):
683 + if stretchedata[i] > threshold:
684 + timelinedata[i] = 1
685 +
686 + if self.iscomments is True:
687 + os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'
688 + service = get_authenticated_service()
689 + comments = get_video_comments(
690 + service, part='snippet', videoId=self.videoID, textFormat='plainText')
691 + video_duration = get_video_duration(
692 + service, part='contentDetails', id=self.videoID)
693 + # print(video_duration)
694 + # print(comments)
695 + self.commentsData = make_video_timeline(comments, video_duration, self.max_slice)
696 + print(str(len(timelinedata)) + "," + str(len(self.commentsData)))
697 + if not (-1 <= len(timelinedata)/self.max_slice - len(self.commentsData) <= 1):
698 + choice = QMessageBox.warning(self, 'warning', '유튜브 댓글 데이터가 올바르지 않습니다. 제외하고 진행하겠습니까?', QMessageBox.Yes | QMessageBox.No)
699 + if choice == QMessageBox.Yes:
700 + self.highlightData = timelinedata
701 + self.iscomments = False
702 + else:
703 + sys.exit()
704 + else: #commentsData반영
705 + for i in range(len(timelinedata)):
706 + #print(i)
707 + if timelinedata[i] == 0:
708 + #print(i/self.max_slice)
709 + if self.commentsData[int(i/self.max_slice)] == 1:
710 + timelinedata[i] = 1
711 +
712 + print(self.commentsData)
713 + self.highlightData = timelinedata
714 +
715 + self.ax1.plot(timelinedata, '*y')
716 + self.ax1.set_xlim((self.stframe / self.fps), int(self.endframe / self.fps))
717 + self.ax1.xaxis.set_ticks(np.arange(self.stframe / self.fps, self.endframe / self.fps, max_slice))
718 + self.ax1.plot([data.to_numpy()[0, 1], data.to_numpy()[0, 1]], [0.5, 1.5], 'k', linewidth=2.0)
719 + self.label_2.canvas.draw()
720 +
721 + #스케일링 방법 바꾸기
722 + return
723 +
724 + def AYBtnClicked(self):
725 + video_id, ok = QInputDialog.getText(self, '영상 ID', '유튜브 영상 ID를 입력하세요. (/watch?v=~)')
726 + if not ok:
727 + return
728 + self.videoID = video_id
729 + self.iscomments = True
730 +
731 +
732 +
733 +
734 +#check on timeline and export
735 +import moviepy.editor as mp
736 +import librosa
737 +
738 +
739 +class Clip:
740 +
741 + def __init__(self, fileName):
742 +
743 + self.clip = mp.VideoFileClip(fileName)
744 + self.fullFileName = fileName
745 + self.fileName = fileName[:fileName.rfind('.')]
746 +
747 + def getLoudSection(self, max_slice=10, threshold=180): #max_slice: 몇 초 단위로 나눌 것인지 결정
748 + self.clip.audio.write_audiofile(self.fileName+'.wav')
749 + x, sr = librosa.load(self.fileName + '.wav', sr=16000) #x: 오디오 파일을 나눈(1초에 sr개로 나눔) 샘플들의 진폭이 저장됨, 샘플의 개수는 sr*영상길이
750 + #print(x)
751 + #print(len(x))
752 + window_length = max_slice * sr #한 구간에 해당하는 샘플의 개수
753 + energy = np.array([sum(abs(x[i: i + window_length] ** 2)) #windwo_length구간 안에있는 모든 샘플들의 진폭의 제곱의 합->소리의 크기
754 + for i in range(0, len(x), window_length)]) #len(x)는 sr*영상길이인데 이것을 window_length만큼 끊음 i = 0, winlength, winlength*2, winlength*3, ...
755 +
756 + #energy data를 dataframe으로 만듦
757 + df = pd.DataFrame(columns=['energy', 'start', 'end'])
758 + row_index = 0
759 + for i in range(len(energy)):
760 + value = energy[i]
761 + i = np.where(energy == value)[0]
762 + df.loc[row_index, 'energy'] = value
763 + df.loc[row_index, 'start'] = i[0] * max_slice
764 + df.loc[row_index, 'end'] = (i[0] + 1) * max_slice
765 + row_index = row_index + 1
766 +
767 + #연속구간으로 나타내기
768 +
769 + return df
770 +
771 +
772 + def concatVideo(self, data, max_slice):
773 + i = 0
774 + clips = []
775 + final1 = []
776 + final2 = []
777 +
778 + while i < len(data):
779 + if data[i] == 1:
780 + subclip = self.clip.subclip(i, i+max_slice)
781 + clips.append(subclip)
782 + i += max_slice
783 +
784 + n= 5
785 + result = [clips[i * n:(i + 1) * n] for i in range((len(clips) + n - 1) // n)]
786 + print(len(result))
787 + for j in range(len(result)):
788 + final1.append(mp.concatenate_videoclips(result[j]))
789 + final2 = mp.concatenate_videoclips(final1)
790 + final2.write_videofile(self.fileName+'_highlights'+'.mp4')
791 +
792 +import pickle
793 +import csv
794 +import os
795 +import pandas as pd
796 +import re
797 +import math
798 +
799 +import google.oauth2.credentials
800 +
801 +from googleapiclient.discovery import build
802 +from googleapiclient.errors import HttpError
803 +from google_auth_oauthlib.flow import InstalledAppFlow
804 +from google.auth.transport.requests import Request
805 +
806 +# parameter 지정
807 +CLIENT_SECRETS_FILE = "/Users/ASUS/Desktop/work/3-1/디자인적사고/project/client_secret.json"
808 +CLIENT_SECRETS_FILE = "/Users/user/Documents/카카오톡 받은 파일/api/client_secret.json"
809 +SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl']
810 +API_SERVICE_NAME = 'youtube'
811 +API_VERSION = 'v3'
812 +
813 +# 인증 모듈
814 +
815 +
816 +def get_authenticated_service():
817 + credentials = None
818 + if os.path.exists('token.pickle'):
819 + with open('token.pickle', 'rb') as token:
820 + credentials = pickle.load(token)
821 + # Check if the credentials are invalid or do not exist
822 + if not credentials or not credentials.valid:
823 + # Check if the credentials have expired
824 + if credentials and credentials.expired and credentials.refresh_token:
825 + credentials.refresh(Request())
826 + else:
827 + flow = InstalledAppFlow.from_client_secrets_file(
828 + CLIENT_SECRETS_FILE, SCOPES)
829 + credentials = flow.run_console()
830 +
831 + # Save the credentials for the next run
832 + with open('token.pickle', 'wb') as token:
833 + pickle.dump(credentials, token)
834 +
835 + return build(API_SERVICE_NAME, API_VERSION, credentials=credentials)
836 +
837 +# video comment 데이터프레임화
838 +
839 +
840 +def get_video_comments(service, **kwargs):
841 + results = service.commentThreads().list(**kwargs).execute()
842 + youtube_pd = pd.DataFrame()
843 + # filename = input('.csv 형태로 입력하세요')
844 + while results:
845 + for item in results['items']:
846 + comment1 = item['snippet']['topLevelComment']['snippet']['textDisplay']
847 + # comment2 = item['snippet']['topLevelComment']['snippet']['publishedAt']
848 + # comment3 = item['snippet']['topLevelComment']['snippet']['authorDisplayName']
849 + # comment2 = item['snippet']['topLevelComment']['snippet']['viewerRating']
850 + comment2 = item['snippet']['topLevelComment']['snippet']['likeCount']
851 +
852 + pd_data = {"comment": comment1, "likeCount": comment2}
853 +
854 + youtube_pd = youtube_pd.append(pd_data, ignore_index=True)
855 + # youtube_pd.to_csv(filename, index=False, encoding='utf-8-sig')
856 + # Check if another page exists
857 + if 'nextPageToken' in results:
858 + kwargs['pageToken'] = results['nextPageToken']
859 + results = service.commentThreads().list(**kwargs).execute()
860 + else:
861 + break
862 +
863 + return youtube_pd
864 +
865 +# 코멘트에서 타임라인만 추출
866 +
867 +
868 +def make_video_timeline(commentFrame, duration, max_slice):
869 + youtube_pd_sorted_by_likeCount = commentFrame.sort_values(by='likeCount')
870 + # .str.split(" ") # .values.tolist()
871 + #youtube_pd_sorted_by_likeCount = youtube_pd_sorted_by_likeCount.head(30) # 갯수 정해서 추출하기
872 + comment = youtube_pd_sorted_by_likeCount['comment']
873 + comment_split = comment.str.split(" ").values.tolist()
874 + result = []
875 + for i in range(len(comment_split)):
876 + for j in range(len(comment_split[i])):
877 + if ":" in comment_split[i][j]:
878 + result.append(comment_split[i][j])
879 + hi = ' '.join(result)
880 + timeline = re.findall(r'(\d{1,2}):(\d{2})', hi)
881 + timelist = []
882 + for groups in timeline:
883 + timelist.append(int(groups[0])*60+int(groups[1]))
884 + timelist.sort()
885 + # print(timelist)
886 + timecheck = []
887 + for i in range(math.ceil(duration/max_slice)):
888 + timecheck.append(0)
889 + for i in range(math.ceil(duration/max_slice)):
890 + for j in timelist:
891 + if i <= int(int(j)/max_slice) <= (i+10/max_slice):
892 + timecheck[i] = 1
893 + # print(timecheck)
894 + return timecheck
895 +
896 +# 비디오 총 길이 추출
897 +
898 +
899 +def get_video_duration(service, **kwargs):
900 + results = service.videos().list(**kwargs).execute()
901 + comment_data = results['items']
902 + comment_data = comment_data[0]['contentDetails']['duration']
903 + match = re.match(r'PT(\d+H)?(\d+M)?(\d+S)?', comment_data).groups()
904 + hours = _js_parseInt(match[0]) if match[0] else 0
905 + minutes = _js_parseInt(match[1]) if match[1] else 0
906 + seconds = _js_parseInt(match[2]) if match[2] else 0
907 + return hours * 3600 + minutes * 60 + seconds
908 +
909 +# 문자열에서 숫자만 추출
910 +
911 +
912 +def _js_parseInt(string):
913 + return int(''.join([x for x in string if x.isdigit()]))
914 +
915 +
916 +
917 +if __name__ == "__main__":
918 + app = QApplication(sys.argv)
919 + myWindow = WindowClass()
920 + myWindow.show()
921 + sys.exit(app.exec_())
922 +