Showing
1 changed file
with
993 additions
and
0 deletions
aditor3.5.py
0 → 100644
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 | + |
-
Please register or login to post a comment