first commit
This commit is contained in:
3
plugin/ffmpeg/__init__.py
Executable file
3
plugin/ffmpeg/__init__.py
Executable file
@@ -0,0 +1,3 @@
|
||||
from .plugin import blueprint, menu, plugin_load, plugin_unload, streaming_kill, get_video_info
|
||||
from .logic import Status, Logic
|
||||
from .interface_program_ffmpeg import Ffmpeg
|
||||
446
plugin/ffmpeg/interface_program_ffmpeg.py
Executable file
446
plugin/ffmpeg/interface_program_ffmpeg.py
Executable file
@@ -0,0 +1,446 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
import threading
|
||||
import subprocess
|
||||
import platform
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pytz import timezone
|
||||
|
||||
# third-party
|
||||
import requests
|
||||
|
||||
|
||||
#from flask import Blueprint, request, Response, send_file, render_template, redirect, jsonify
|
||||
|
||||
# sjva 공용
|
||||
from framework.logger import get_logger
|
||||
from framework.util import Util
|
||||
from framework import app
|
||||
|
||||
# 패키지
|
||||
from ffmpeg.logic import Logic, Status
|
||||
from ffmpeg.model import ModelSetting
|
||||
|
||||
# 로그
|
||||
package_name = __name__.split('.')[0]
|
||||
logger = get_logger(package_name)
|
||||
#########################################################
|
||||
|
||||
|
||||
class Ffmpeg(object):
|
||||
instance_list = []
|
||||
idx = 1
|
||||
# retry : 재시도 횟수
|
||||
# max_error_packet_count : 이 숫자 초과시 중단
|
||||
# where : 호출 모듈
|
||||
def __init__(self, url, filename, plugin_id=None, listener=None, max_pf_count=None, call_plugin=None, temp_path=None, save_path=None, proxy=None, headers=None):
|
||||
self.thread = None
|
||||
self.url = url
|
||||
self.filename = filename
|
||||
self.plugin_id = plugin_id
|
||||
self.listener = listener
|
||||
self.max_pf_count = int(ModelSetting.query.filter_by(key='max_pf_count').first().value if max_pf_count is None else max_pf_count)
|
||||
self.call_plugin = call_plugin
|
||||
self.process = None
|
||||
self.temp_path = ModelSetting.query.filter_by(key='temp_path').first().value if temp_path is None else temp_path
|
||||
self.save_path = ModelSetting.query.filter_by(key='save_path').first().value if save_path is None else save_path
|
||||
self.proxy = proxy
|
||||
self.temp_fullpath = os.path.join(self.temp_path, filename)
|
||||
self.save_fullpath = os.path.join(self.save_path, filename)
|
||||
self.log_thread = None
|
||||
self.status = Status.READY
|
||||
self.duration = 0
|
||||
self.duration_str = ''
|
||||
self.current_duration = 0
|
||||
self.percent = 0
|
||||
#self.log = []
|
||||
self.current_pf_count = 0
|
||||
self.idx = str(Ffmpeg.idx)
|
||||
Ffmpeg.idx += 1
|
||||
self.current_bitrate = ''
|
||||
self.current_speed = ''
|
||||
self.start_time = None
|
||||
self.end_time = None
|
||||
self.download_time = None
|
||||
self.start_event = threading.Event()
|
||||
self.exist = False
|
||||
self.filesize = 0
|
||||
self.filesize_str = ''
|
||||
self.download_speed = ''
|
||||
self.headers = headers
|
||||
|
||||
Ffmpeg.instance_list.append(self)
|
||||
#logger.debug('Ffmpeg.instance_list LEN:%s', len(Ffmpeg.instance_list))
|
||||
if len(Ffmpeg.instance_list) > 30:
|
||||
for f in Ffmpeg.instance_list:
|
||||
if f.thread is None and f.status != Status.READY:
|
||||
Ffmpeg.instance_list.remove(f)
|
||||
break
|
||||
else:
|
||||
logger.debug('remove fail %s %s', f.thread, self.status)
|
||||
|
||||
def start(self):
|
||||
self.thread = threading.Thread(target=self.thread_fuction, args=())
|
||||
self.thread.start()
|
||||
#self.start_event.wait(timeout=10)
|
||||
self.start_time = datetime.now()
|
||||
return self.get_data()
|
||||
|
||||
def start_and_wait(self):
|
||||
self.start()
|
||||
self.thread.join(timeout=60*70)
|
||||
|
||||
def stop(self):
|
||||
try:
|
||||
self.status = Status.USER_STOP
|
||||
self.kill()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
def kill(self):
|
||||
try:
|
||||
if self.process is not None and self.process.poll() is None:
|
||||
import psutil
|
||||
process = psutil.Process(self.process.pid)
|
||||
for proc in process.children(recursive=True):
|
||||
proc.kill()
|
||||
process.kill()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
def thread_fuction(self):
|
||||
try:
|
||||
import system
|
||||
user = str(system.ModelSetting.get('sjva_me_user_id'))
|
||||
try:
|
||||
from framework.common.util import AESCipher
|
||||
user = AESCipher.encrypt(user)
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
if self.proxy is None:
|
||||
if self.headers is None:
|
||||
command = [ModelSetting.get('ffmpeg_path'), '-y', '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc', '-metadata', 'network=%s' % user]
|
||||
else:
|
||||
headers_command = []
|
||||
for key, value in self.headers.items():
|
||||
if key.lower() == 'user-agent':
|
||||
headers_command.append('-user_agent')
|
||||
headers_command.append(value)
|
||||
pass
|
||||
else:
|
||||
headers_command.append('-headers')
|
||||
#headers_command.append('\'%s: "%s"\''%(key,value))
|
||||
if platform.system() == 'Windows':
|
||||
headers_command.append('\'%s:%s\''%(key,value))
|
||||
else:
|
||||
headers_command.append(f'{key}:{value}')
|
||||
command = [ModelSetting.get('ffmpeg_path'), '-y'] + headers_command + ['-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc', '-metadata', 'network=%s' % user]
|
||||
else:
|
||||
command = [ModelSetting.get('ffmpeg_path'), '-y', '-http_proxy', self.proxy, '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc', '-metadata', 'network=%s' % user]
|
||||
#command = [ModelSetting.get('ffmpeg_path'), '-y', '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
|
||||
if platform.system() == 'Windows':
|
||||
#if self.call_plugin.startswith('pooq'):
|
||||
now = str(datetime.now()).replace(':', '').replace('-', '').replace(' ', '-')
|
||||
#filename = ('%s' % now).split('.')[0] + '.mp4'
|
||||
#동시에 요청시 구분이 안됨. 2019-10-11
|
||||
filename = ('%s' % now) + '.mp4'
|
||||
self.temp_fullpath = os.path.join(self.temp_path, filename)
|
||||
command.append(self.temp_fullpath)
|
||||
#else:
|
||||
# command.append(self.temp_fullpath.encode('cp949'))
|
||||
else:
|
||||
command.append(self.temp_fullpath)
|
||||
|
||||
try:
|
||||
logger.debug(' '.join(command))
|
||||
#logger.debug(command)
|
||||
if os.path.exists(self.temp_fullpath):
|
||||
for f in Ffmpeg.instance_list:
|
||||
if f.idx != self.idx and f.temp_fullpath == self.temp_fullpath and f.status in [Status.DOWNLOADING, Status.READY]:
|
||||
self.status = Status.ALREADY_DOWNLOADING
|
||||
#logger.debug('temp_fullpath ALREADY_DOWNLOADING')
|
||||
return
|
||||
except:
|
||||
pass
|
||||
#self.process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1)
|
||||
self.process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf8')
|
||||
|
||||
self.status = Status.READY
|
||||
self.log_thread = threading.Thread(target=self.log_thread_fuction, args=())
|
||||
self.log_thread.start()
|
||||
self.start_event.wait(timeout=60)
|
||||
#logger.debug('start_event awake.. ')
|
||||
if self.log_thread is None:
|
||||
#logger.debug('log_thread is none')
|
||||
#logger.debug(self.log)
|
||||
if self.status == Status.READY:
|
||||
self.status = Status.ERROR
|
||||
self.kill()
|
||||
elif self.status == Status.READY:
|
||||
#logger.debug('still status.ready kill')
|
||||
#logger.debug(self.log)
|
||||
self.status = Status.ERROR
|
||||
self.kill()
|
||||
else:
|
||||
#logger.debug('normally process wait()')
|
||||
#process_ret = self.process.wait(timeout=60*60)
|
||||
process_ret = self.process.wait(timeout=60*ModelSetting.get_int('timeout_minute'))
|
||||
#logger.debug('process_ret :%s' % process_ret)
|
||||
if process_ret is None: # timeout
|
||||
if self.status != Status.COMPLETED and self.status != Status.USER_STOP and self.status != Status.PF_STOP:
|
||||
self.status = Status.TIME_OVER
|
||||
self.kill()
|
||||
else:
|
||||
#logger.debug('process end')
|
||||
if self.status == Status.DOWNLOADING:
|
||||
self.status = Status.FORCE_STOP
|
||||
self.end_time = datetime.now()
|
||||
self.download_time = self.end_time - self.start_time
|
||||
try:
|
||||
if self.status == Status.COMPLETED:
|
||||
if self.save_fullpath != self.temp_fullpath:
|
||||
if os.path.exists(self.save_fullpath):
|
||||
os.remove(self.save_fullpath)
|
||||
#if app.config['config']['is_py2']:
|
||||
# os.chmod(self.temp_fullpath, 0777)
|
||||
#else:
|
||||
# os.chmod(self.temp_fullpath, 777)
|
||||
os.system('chmod 777 "%s"' % self.temp_fullpath)
|
||||
# 2020-03-19 씹힌다 ㅠ
|
||||
#import framework.common.celery as celery_task
|
||||
#celery_task.move(self.temp_fullpath, self.save_fullpath)
|
||||
import shutil
|
||||
shutil.move(self.temp_fullpath, self.save_fullpath)
|
||||
|
||||
# 2019-05-16
|
||||
#os.chmod(self.save_fullpath, 0777)
|
||||
self.filesize = os.stat(self.save_fullpath).st_size
|
||||
else:
|
||||
if os.path.exists(self.temp_fullpath):
|
||||
os.remove(self.temp_fullpath)
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
arg = {'type':'last', 'status':self.status, 'data' : self.get_data()}
|
||||
self.send_to_listener(**arg)
|
||||
self.process = None
|
||||
self.thread = None
|
||||
|
||||
#파일처리
|
||||
|
||||
#logger.debug('ffmpeg thread end')
|
||||
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
try:
|
||||
self.status = Status.EXCEPTION
|
||||
arg = {'type':'last', 'status':self.status, 'data' : self.get_data()}
|
||||
self.send_to_listener(**arg)
|
||||
except:
|
||||
pass
|
||||
#self.start_event.set()
|
||||
|
||||
|
||||
def log_thread_fuction(self):
|
||||
with self.process.stdout:
|
||||
iter_arg = b'' if app.config['config']['is_py2'] else ''
|
||||
for line in iter(self.process.stdout.readline, iter_arg):
|
||||
try:
|
||||
#logger.debug(line)
|
||||
#logger.debug('XXXXXXXXXXXXXX %s', self.status)
|
||||
#self.log.append(line.strip())
|
||||
#arg = {'type':'log', 'status':self.status, 'data' : {'idx':self.idx, 'line':line}}
|
||||
#self.send_to_listener(**arg)
|
||||
if self.status == Status.READY:
|
||||
if line.find('Server returned 404 Not Found') != -1 or line.find('Unknown error') != -1:
|
||||
self.status = Status.WRONG_URL
|
||||
self.start_event.set()
|
||||
#logger.debug('start_event set by 404 not found')
|
||||
elif line.find('No such file or directory') != -1:
|
||||
self.status = Status.WRONG_DIRECTORY
|
||||
self.start_event.set()
|
||||
#logger.debug('start_event set by WRONG_DIR')
|
||||
else:
|
||||
match = re.compile(r'Duration\:\s(\d{2})\:(\d{2})\:(\d{2})\.(\d{2})\,\sstart').search(line)
|
||||
if match:
|
||||
self.duration_str = '%s:%s:%s' % ( match.group(1), match.group(2), match.group(3))
|
||||
self.duration = int(match.group(4))
|
||||
self.duration += int(match.group(3)) * 100
|
||||
self.duration += int(match.group(2)) * 100 * 60
|
||||
self.duration += int(match.group(1)) * 100 * 60 * 60
|
||||
#logger.debug('Duration : %s', self.duration)
|
||||
if match:
|
||||
self.status = Status.READY
|
||||
arg = {'type':'status_change', 'status':self.status, 'data' : self.get_data()}
|
||||
self.send_to_listener(**arg)
|
||||
continue
|
||||
#if self.listener is not None:
|
||||
# #arg = {'status':self.status, 'duration' : self.duration}
|
||||
# arg = {'status':self.status, 'data' : self.get_data()}
|
||||
# self.listener(**arg)
|
||||
#
|
||||
#self.status = 'DOWNLOADING'
|
||||
match = re.compile(r'time\=(\d{2})\:(\d{2})\:(\d{2})\.(\d{2})\sbitrate\=\s*(?P<bitrate>\d+).*?[$|\s](\s?speed\=\s*(?P<speed>.*?)x)?').search(line)
|
||||
if match:
|
||||
self.status = Status.DOWNLOADING
|
||||
arg = {'type':'status_change', 'status':self.status, 'data' : self.get_data()}
|
||||
self.send_to_listener(**arg)
|
||||
self.start_event.set()
|
||||
#logger.debug('start_event set by DOWNLOADING')
|
||||
elif self.status == Status.DOWNLOADING:
|
||||
if line.find('PES packet size mismatch') != -1:
|
||||
self.current_pf_count += 1
|
||||
if self.current_pf_count > self.max_pf_count:
|
||||
#logger.debug('%s : PF_STOP!', self.idx)
|
||||
self.status = Status.PF_STOP
|
||||
self.kill()
|
||||
continue
|
||||
if line.find('HTTP error 403 Forbidden') != -1:
|
||||
#logger.debug('HTTP error 403 Forbidden')
|
||||
self.status = Status.HTTP_FORBIDDEN
|
||||
self.kill()
|
||||
continue
|
||||
match = re.compile(r'time\=(\d{2})\:(\d{2})\:(\d{2})\.(\d{2})\sbitrate\=\s*(?P<bitrate>\d+).*?[$|\s](\s?speed\=\s*(?P<speed>.*?)x)?').search(line)
|
||||
if match:
|
||||
self.current_duration = int(match.group(4))
|
||||
self.current_duration += int(match.group(3)) * 100
|
||||
self.current_duration += int(match.group(2)) * 100 * 60
|
||||
self.current_duration += int(match.group(1)) * 100 * 60 * 60
|
||||
try:
|
||||
self.percent = int(self.current_duration * 100 / self.duration)
|
||||
except: pass
|
||||
#logger.debug('Current Duration : %s %s %s%%', self.duration, self.current_duration, self.percent)
|
||||
self.current_bitrate = match.group('bitrate')
|
||||
self.current_speed = match.group('speed')
|
||||
self.download_time = datetime.now() - self.start_time
|
||||
arg = {'type':'normal', 'status':self.status, 'data' : self.get_data()}
|
||||
self.send_to_listener(**arg)
|
||||
continue
|
||||
match = re.compile(r'video\:\d*kB\saudio\:\d*kB').search(line)
|
||||
if match:
|
||||
self.status = Status.COMPLETED
|
||||
self.end_time = datetime.now()
|
||||
self.download_time = self.end_time - self.start_time
|
||||
self.percent = 100
|
||||
arg = {'type':'status_change', 'status':self.status, 'data' : self.get_data()}
|
||||
self.send_to_listener(**arg)
|
||||
continue
|
||||
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
#logger.debug('ffmpeg log thread end')
|
||||
self.start_event.set()
|
||||
self.log_thread = None
|
||||
|
||||
def get_data(self):
|
||||
data = {
|
||||
'url' : self.url,
|
||||
'filename' : self.filename,
|
||||
'max_pf_count' : self.max_pf_count,
|
||||
'call_plugin' : self.call_plugin,
|
||||
'temp_path' : self.temp_path,
|
||||
'save_path' : self.save_path,
|
||||
'temp_fullpath' : self.temp_fullpath,
|
||||
'save_fullpath' : self.save_fullpath,
|
||||
'status' : int(self.status),
|
||||
'status_str' : self.status.name,
|
||||
'status_kor' : str(self.status),
|
||||
'duration' : self.duration,
|
||||
'duration_str' : self.duration_str,
|
||||
'current_duration' : self.current_duration,
|
||||
'percent' : self.percent,
|
||||
'current_pf_count' : self.current_pf_count,
|
||||
'idx' : self.idx,
|
||||
#'log' : self.log,
|
||||
'current_bitrate' : self.current_bitrate,
|
||||
'current_speed' : self.current_speed,
|
||||
'start_time' : '' if self.start_time is None else str(self.start_time).split('.')[0][5:],
|
||||
'end_time' : '' if self.end_time is None else str(self.end_time).split('.')[0][5:],
|
||||
'download_time' : '' if self.download_time is None else '%02d:%02d' % (self.download_time.seconds/60, self.download_time.seconds%60),
|
||||
'exist' : os.path.exists(self.save_fullpath),
|
||||
}
|
||||
if self.status == Status.COMPLETED:
|
||||
data['filesize'] = self.filesize
|
||||
data['filesize_str'] = Util.sizeof_fmt(self.filesize)
|
||||
data['download_speed'] = Util.sizeof_fmt(self.filesize/self.download_time.seconds, suffix='Bytes/Second')
|
||||
return data
|
||||
|
||||
def send_to_listener(self, **arg):
|
||||
Logic.ffmpeg_listener(**arg)
|
||||
if self.listener is not None:
|
||||
arg['plugin_id'] = self.plugin_id
|
||||
self.listener(**arg)
|
||||
|
||||
@staticmethod
|
||||
def get_version():
|
||||
try:
|
||||
command = u'%s -version' % (ModelSetting.get('ffmpeg_path'))
|
||||
command = command.split(' ')
|
||||
#logger.debug(command)
|
||||
if app.config['config']['is_py2']:
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, bufsize=1)
|
||||
else:
|
||||
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, encoding='utf8')
|
||||
iter_arg = b'' if app.config['config']['is_py2'] else ''
|
||||
ret = []
|
||||
with process.stdout:
|
||||
for line in iter(process.stdout.readline, iter_arg):
|
||||
ret.append(line)
|
||||
process.wait() # wait for the subprocess to exit
|
||||
return ret
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def stop_by_idx(idx):
|
||||
try:
|
||||
for f in Ffmpeg.instance_list:
|
||||
if f.idx == idx:
|
||||
f.stop()
|
||||
break
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def ffmpeg_by_idx(idx):
|
||||
try:
|
||||
for f in Ffmpeg.instance_list:
|
||||
if f.idx == idx:
|
||||
return f
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_ffmpeg_by_caller(caller, caller_id):
|
||||
try:
|
||||
for f in Ffmpeg.instance_list:
|
||||
if f.plugin_id == caller_id and f.call_plugin == caller:
|
||||
return f
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def plugin_unload():
|
||||
try:
|
||||
for f in Ffmpeg.instance_list:
|
||||
f.stop()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
178
plugin/ffmpeg/logic.py
Executable file
178
plugin/ffmpeg/logic.py
Executable file
@@ -0,0 +1,178 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
from datetime import datetime
|
||||
import traceback
|
||||
import logging
|
||||
import subprocess
|
||||
import time
|
||||
import re
|
||||
import threading
|
||||
import json
|
||||
import enum
|
||||
import platform
|
||||
|
||||
# third-party
|
||||
from sqlalchemy import desc
|
||||
from sqlalchemy import or_, and_, func, not_
|
||||
|
||||
# sjva 공용
|
||||
from framework.logger import get_logger
|
||||
from framework import app, db, scheduler, path_app_root, socketio, path_data
|
||||
from framework.job import Job
|
||||
from framework.util import Util
|
||||
from system.logic import SystemLogic
|
||||
|
||||
# 패키지
|
||||
from .model import ModelSetting
|
||||
|
||||
|
||||
# 로그
|
||||
package_name = __name__.split('.')[0]
|
||||
logger = get_logger(package_name)
|
||||
#########################################################
|
||||
|
||||
class Status(enum.Enum):
|
||||
READY = 0
|
||||
WRONG_URL = 1
|
||||
WRONG_DIRECTORY = 2
|
||||
EXCEPTION = 3
|
||||
ERROR = 4
|
||||
HTTP_FORBIDDEN = 11
|
||||
|
||||
DOWNLOADING = 5
|
||||
|
||||
USER_STOP = 6
|
||||
COMPLETED = 7
|
||||
TIME_OVER = 8
|
||||
PF_STOP = 9
|
||||
FORCE_STOP = 10 #강제중단
|
||||
ALREADY_DOWNLOADING = 12 #이미 목록에 있고 다운로드중
|
||||
|
||||
def __int__(self):
|
||||
return self.value
|
||||
|
||||
def __str__(self):
|
||||
kor = [r'준비', r'URL에러', r'폴더에러', r'실패(Exception)', r'실패(에러)', r'다운로드중', r'사용자중지', r'완료', r'시간초과', r'PF중지', r'강제중지',
|
||||
r'403에러', r'임시파일이 이미 있음']
|
||||
return kor[int(self)]
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
@staticmethod
|
||||
def get_instance(value):
|
||||
tmp = [ Status.READY, Status.WRONG_URL, Status.WRONG_DIRECTORY, Status.EXCEPTION, Status.ERROR,
|
||||
Status.DOWNLOADING, Status.USER_STOP, Status.COMPLETED, Status.TIME_OVER, Status.PF_STOP,
|
||||
Status.FORCE_STOP, Status.HTTP_FORBIDDEN, Status.ALREADY_DOWNLOADING ]
|
||||
return tmp[value]
|
||||
|
||||
class Logic(object):
|
||||
db_default = {
|
||||
'temp_path' : os.path.join(path_data, 'tmp'),
|
||||
'save_path' : os.path.join(path_data, 'download'),
|
||||
'max_pf_count' : '0',
|
||||
'if_fail_remove_tmp_file' : 'True',
|
||||
'timeout_minute' : '60',
|
||||
'ffmpeg_path' : 'ffmpeg' if platform.system() != 'Windows' else os.path.join(path_data, 'bin', 'ffmpeg.exe'),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def db_init():
|
||||
try:
|
||||
for key, value in Logic.db_default.items():
|
||||
if db.session.query(ModelSetting).filter_by(key=key).count() == 0:
|
||||
db.session.add(ModelSetting(key, value))
|
||||
db.session.commit()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def plugin_load():
|
||||
try:
|
||||
Logic.db_init()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def plugin_unload():
|
||||
try:
|
||||
from ffmpeg.interface_program_ffmpeg import Ffmpeg
|
||||
Ffmpeg.plugin_unload()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def ffmpeg_listener(**args):
|
||||
#logger.debug('ffmpeg_listener : %s', args)
|
||||
#logger.debug('ffmpeg_listener : type:%s status:%s', args['type'], args['status'])
|
||||
# status_change ready, downloading, completed
|
||||
refresh_type = None
|
||||
if args['type'] == 'status_change':
|
||||
if args['status'] == Status.DOWNLOADING:
|
||||
refresh_type = 'status_change'
|
||||
elif args['status'] == Status.COMPLETED:
|
||||
refresh_type = 'status_change'
|
||||
elif args['status'] == Status.READY:
|
||||
data = {'type':'info', 'msg' : u'다운로드중 Duration(%s)' % args['data']['duration_str'] + '<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
# 1번에서 리스트화면, 2번에서 추가시 1번도 추가되도록
|
||||
refresh_type = 'add'
|
||||
elif args['type'] == 'last':
|
||||
|
||||
if args['status'] == Status.WRONG_URL:
|
||||
data = {'type':'warning', 'msg' : u'잘못된 URL입니다'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'add'
|
||||
elif args['status'] == Status.WRONG_DIRECTORY:
|
||||
data = {'type':'warning', 'msg' : u'잘못된 디렉토리입니다.<br>' + args['data']['save_fullpath']}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'add'
|
||||
elif args['status'] == Status.ERROR or args['status'] == Status.EXCEPTION:
|
||||
data = {'type':'warning', 'msg' : u'다운로드 시작 실패.<br>' + args['data']['save_fullpath']}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'add'
|
||||
elif args['status'] == Status.USER_STOP:
|
||||
data = {'type':'warning', 'msg' : u'다운로드가 중지 되었습니다.<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
elif args['status'] == Status.COMPLETED:
|
||||
data = {'type':'success', 'msg' : u'다운로드가 완료 되었습니다.<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
elif args['status'] == Status.TIME_OVER:
|
||||
data = {'type':'warning', 'msg' : u'시간초과로 중단 되었습니다.<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
elif args['status'] == Status.PF_STOP:
|
||||
data = {'type':'warning', 'msg' : u'PF초과로 중단 되었습니다.<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
elif args['status'] == Status.FORCE_STOP:
|
||||
data = {'type':'warning', 'msg' : u'강제 중단 되었습니다.<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
elif args['status'] == Status.HTTP_FORBIDDEN:
|
||||
data = {'type':'warning', 'msg' : u'403에러로 중단 되었습니다.<br>' + args['data']['save_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
elif args['status'] == Status.ALREADY_DOWNLOADING:
|
||||
data = {'type':'warning', 'msg' : u'임시파일폴더에 파일이 있습니다.<br>' + args['data']['temp_fullpath'], 'url':'/ffmpeg/list'}
|
||||
socketio.emit("notify", data, namespace='/framework', broadcast=True)
|
||||
refresh_type = 'last'
|
||||
|
||||
|
||||
|
||||
#elif args['type'] == 'log':
|
||||
# socketio.emit("log", args['data'], namespace='/%s' % package_name, broadcast=True)
|
||||
|
||||
elif args['type'] == 'normal':
|
||||
if args['status'] == Status.DOWNLOADING:
|
||||
refresh_type = 'status'
|
||||
|
||||
if refresh_type is not None:
|
||||
socketio.emit(refresh_type, args['data'], namespace='/%s' % package_name, broadcast=True)
|
||||
23
plugin/ffmpeg/model.py
Executable file
23
plugin/ffmpeg/model.py
Executable file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
# third-party
|
||||
from sqlalchemy import or_, and_, func, not_
|
||||
from sqlalchemy.orm import backref
|
||||
|
||||
# sjva 공용
|
||||
from framework import db, app, path_data
|
||||
from framework.util import Util
|
||||
|
||||
# 패키지
|
||||
from .plugin import logger, package_name
|
||||
#########################################################
|
||||
app.config['SQLALCHEMY_BINDS'][package_name] = 'sqlite:///%s' % (os.path.join(path_data, 'db', '%s.db' % package_name))
|
||||
from plugin import get_model_setting
|
||||
ModelSetting = get_model_setting(package_name, logger)
|
||||
|
||||
323
plugin/ffmpeg/plugin.py
Executable file
323
plugin/ffmpeg/plugin.py
Executable file
@@ -0,0 +1,323 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#########################################################
|
||||
# python
|
||||
import os
|
||||
import traceback
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pytz import timezone
|
||||
|
||||
# third-party
|
||||
import requests
|
||||
from flask import Blueprint, request, Response, send_file, render_template, redirect, jsonify, session, send_from_directory, stream_with_context
|
||||
from flask_socketio import SocketIO, emit, send
|
||||
from flask_login import login_user, logout_user, current_user, login_required
|
||||
# sjva 공용
|
||||
from framework import app, db, scheduler, path_app_root, socketio, path_data
|
||||
from framework.logger import get_logger
|
||||
from framework.util import Util
|
||||
|
||||
# 패키지
|
||||
package_name = __name__.split('.')[0]
|
||||
logger = get_logger(package_name)
|
||||
from ffmpeg.logic import Logic
|
||||
from ffmpeg.model import ModelSetting
|
||||
from ffmpeg.interface_program_ffmpeg import Ffmpeg
|
||||
from system.model import ModelSetting as SystemModelSetting
|
||||
|
||||
|
||||
#########################################################
|
||||
|
||||
|
||||
#########################################################
|
||||
# 플러그인 공용
|
||||
#########################################################
|
||||
blueprint = Blueprint(package_name, package_name, url_prefix='/%s' % package_name, template_folder='templates')
|
||||
menu = {
|
||||
'main' : [package_name, u'FFMPEG'],
|
||||
'sub' : [
|
||||
['setting', u'설정'], ['download', u'다운로드'], ['list', u'목록'], ['log', u'로그'],
|
||||
]
|
||||
}
|
||||
|
||||
def plugin_load():
|
||||
Logic.plugin_load()
|
||||
|
||||
|
||||
def plugin_unload():
|
||||
Logic.plugin_unload()
|
||||
streaming_kill()
|
||||
|
||||
def streaming_kill():
|
||||
logger.debug('streaming_kill...')
|
||||
global process_list
|
||||
try:
|
||||
for p in process_list:
|
||||
if p is not None and p.poll() is None:
|
||||
import psutil
|
||||
process = psutil.Process(p.pid)
|
||||
for proc in process.children(recursive=True):
|
||||
proc.kill()
|
||||
process.kill()
|
||||
except Exception as e:
|
||||
logger.error('Exception:%s', e)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
#########################################################
|
||||
# WEB Menu
|
||||
#########################################################
|
||||
@blueprint.route('/')
|
||||
def home():
|
||||
return redirect('/%s/list' % package_name)
|
||||
|
||||
@blueprint.route('/<sub>')
|
||||
@login_required
|
||||
def detail(sub):
|
||||
arg = ModelSetting.to_dict()
|
||||
if sub == 'setting':
|
||||
return render_template('{package_name}_{sub}.html'.format(package_name=package_name, sub=sub), arg=arg)
|
||||
elif sub == 'download':
|
||||
now = str(datetime.now(timezone('Asia/Seoul'))).replace(':', '').replace('-', '').replace(' ', '-')
|
||||
arg['temp_filename'] = ('%s' % now).split('.')[0] + '.mp4'
|
||||
return render_template('{package_name}_{sub}.html'.format(package_name=package_name, sub=sub), arg=arg)
|
||||
elif sub == 'list':
|
||||
return render_template('{package_name}_{sub}.html'.format(package_name=package_name, sub=sub), arg=arg)
|
||||
elif sub == 'log':
|
||||
return render_template('log.html', package=package_name)
|
||||
return render_template('sample.html', title='%s - %s' % (package_name, sub))
|
||||
|
||||
#########################################################
|
||||
# For UI
|
||||
#########################################################
|
||||
@blueprint.route('/ajax/<sub>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def ajax(sub):
|
||||
try:
|
||||
if sub == 'setting_save':
|
||||
ret = ModelSetting.setting_save(request)
|
||||
return jsonify(ret)
|
||||
elif sub == 'ffmpeg_version':
|
||||
ret = Ffmpeg.get_version()
|
||||
return jsonify(ret)
|
||||
elif sub == 'download':
|
||||
url = request.form['url']
|
||||
filename = request.form['filename']
|
||||
ffmpeg = Ffmpeg(url, filename, call_plugin=package_name)
|
||||
data = ffmpeg.start()
|
||||
return jsonify([])
|
||||
elif sub == 'stop':
|
||||
idx = request.form['idx']
|
||||
Ffmpeg.stop_by_idx(idx)
|
||||
return jsonify([])
|
||||
elif sub == 'play':
|
||||
idx = request.form['idx']
|
||||
ffmpeg = Ffmpeg.ffmpeg_by_idx(idx)
|
||||
tmp = ffmpeg.save_fullpath.replace(path_app_root, '')
|
||||
tmp = tmp.replace('\\', '/')
|
||||
logger.debug('play : %s', tmp)
|
||||
#return redirect('/open_file%s', tmp)
|
||||
#return send_from_directory('', tmp[1:])
|
||||
return jsonify(tmp)
|
||||
elif sub == 'list':
|
||||
ret = []
|
||||
for ffmpeg in Ffmpeg.instance_list:
|
||||
ret.append(ffmpeg.get_data())
|
||||
return jsonify(ret)
|
||||
elif sub == 'streaming_kill':
|
||||
streaming_kill()
|
||||
return jsonify('')
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
#http://192.168.0.11:9999/ffmpeg/api/download?url=https%3a%2f%2fani24zo.com%2fani%2fdownload.php%3fid%3d38912&filename=test.mp4&id=0&caller=ani24&save_path=D:\
|
||||
#http://192.168.0.11:9999/ffmpeg/api/status?id=0&caller=ani24
|
||||
#http://192.168.0.11:9999/ffmpeg/api/stop?id=0&caller=ani24
|
||||
|
||||
|
||||
@blueprint.route('/api/<sub>', methods=['GET', 'POST'])
|
||||
def api(sub):
|
||||
sjva_token = request.args.get('token')
|
||||
if sjva_token != SystemModelSetting.get('unique'):
|
||||
ret = {}
|
||||
ret['ret'] = 'wrong_token'
|
||||
return jsonify(ret)
|
||||
if sub == 'download':
|
||||
ret = {}
|
||||
try:
|
||||
|
||||
max_pf_count = ModelSetting.get('max_pf_count')
|
||||
url = request.args.get('url')
|
||||
filename = request.args.get('filename')
|
||||
caller_id = request.args.get('id')
|
||||
package_name = request.args.get('caller')
|
||||
save_path = request.args.get('save_path')
|
||||
if save_path is None:
|
||||
save_path = ModelSetting.get('save_path')
|
||||
else:
|
||||
if not os.path.exists(save_path):
|
||||
os.makedirs(save_path)
|
||||
|
||||
logger.debug('url : %s', url)
|
||||
logger.debug('filename : %s', filename)
|
||||
logger.debug('caller_id : %s', caller_id)
|
||||
logger.debug('caller : %s', package_name)
|
||||
logger.debug('save_path : %s', save_path)
|
||||
|
||||
f = Ffmpeg(url, filename, plugin_id=caller_id, listener=None, max_pf_count=max_pf_count, call_plugin=package_name, save_path=save_path)
|
||||
f.start()
|
||||
ret['ret'] = 'success'
|
||||
ret['data'] = f.get_data()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
ret['ret'] = 'exception'
|
||||
ret['log'] = traceback.format_exc()
|
||||
return jsonify(ret)
|
||||
elif sub == 'stop':
|
||||
ret = {}
|
||||
try:
|
||||
caller_id = request.args.get('id')
|
||||
package_name = request.args.get('caller')
|
||||
f = Ffmpeg.get_ffmpeg_by_caller(package_name, caller_id)
|
||||
Ffmpeg.stop_by_idx(f.idx)
|
||||
ret['ret'] = 'success'
|
||||
ret['data'] = f.get_data()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
ret['ret'] = 'exception'
|
||||
ret['log'] = traceback.format_exc()
|
||||
return jsonify(ret)
|
||||
elif sub == 'status':
|
||||
ret = {}
|
||||
try:
|
||||
caller_id = request.args.get('id')
|
||||
package_name = request.args.get('caller')
|
||||
f = Ffmpeg.get_ffmpeg_by_caller(package_name, caller_id)
|
||||
ret['ret'] = 'success'
|
||||
ret['data'] = f.get_data()
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
ret['ret'] = 'exception'
|
||||
ret['log'] = traceback.format_exc()
|
||||
return jsonify(ret)
|
||||
|
||||
|
||||
|
||||
|
||||
@socketio.on('connect', namespace='/%s' % package_name)
|
||||
def connect():
|
||||
logger.debug('ffmpeg socketio connect')
|
||||
|
||||
@socketio.on('disconnect', namespace='/%s' % package_name)
|
||||
def disconnect():
|
||||
logger.debug('ffmpeg socketio disconnect')
|
||||
|
||||
|
||||
process_list = []
|
||||
@blueprint.route('/streaming', methods=['GET'])
|
||||
def streaming():
|
||||
mode = request.args.get('mode')
|
||||
if mode == 'file':
|
||||
try:
|
||||
import subprocess
|
||||
filename = request.args.get('value')
|
||||
if filename.endswith('mp4'):
|
||||
"""
|
||||
output = os.path.join(path_data, 'tmp', 'index.m3u8')
|
||||
#ffmpeg_command = ['ffmpeg', "-loglevel", "quiet", "-i", filename, '-ss', '00:00:03', '-t', '00:03:00', "-vcodec", 'libx264', '-vf', 'scale=160:-1', '-qscale:v', '1', '-acodec', 'aac', '-qscale:a', '1', '-f', 'mp4', output, '-y']
|
||||
ffmpeg_command = ['ffmpeg', "-loglevel", "quiet", "-i", filename, '-ss', '00:00:03', '-t', '00:03:00', "-start_number", '0', '-vf', 'scale=320:-1', '-hls_list_size', '0', '-hls_time', '10', '-f', 'hls', output, '-y']
|
||||
|
||||
logger.warning(' '.join(ffmpeg_command))
|
||||
#subprocess.check_output(ffmpeg_command)
|
||||
subprocess.Popen(ffmpeg_command)
|
||||
time.sleep(5)
|
||||
|
||||
filename = output
|
||||
"""
|
||||
|
||||
url = '/open_file%s' % filename
|
||||
logger.debug(url)
|
||||
return redirect(url)
|
||||
|
||||
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
|
||||
def generate():
|
||||
startTime = time.time()
|
||||
buffer = []
|
||||
sentBurst = False
|
||||
|
||||
path_ffmpeg = 'ffmpeg'
|
||||
#filename = '/home/coder/project/SJ/mnt/soju6janm/AV/censored/library2/vr/C/CBIKMV/CBIKMV-093/cbikmv-093cd1.mp4'
|
||||
#filename = '/home/coder/project/SJ/mnt/soju6janw/1.mp4'
|
||||
#ffmpeg_command = [path_ffmpeg, "-loglevel", "quiet", "-i", filename, "-c:v", "copy", "-c:a", "aac", "-b:a", "128k", "-f", "mpegts", "-tune", "zerolatency", "pipe:stdout"]
|
||||
|
||||
ffmpeg_command = [path_ffmpeg, "-loglevel", "quiet", "-i", filename, '-ss', '00:00:03', '-t', '00:03:00', "-vcodec", "libvpx", '-vf', 'scale=320:-1', "-qmin", "0", "-qmax", "50", "-crf", "50", "-b:v", "0.1M", '-acodec', 'libvorbis', '-f', 'webm', "pipe:stdout"]
|
||||
|
||||
#ffmpeg_command = [path_ffmpeg, "-loglevel", "quiet", "-i", filename, "-vcodec", "libtheora", '-vf', 'scale=320:-1', "-qscale:v", '1', '-acodec', 'libvorbis', '-qscale:a', '1', '-f', 'ogv', "pipe:stdout"]
|
||||
|
||||
#ffmpeg_command = [path_ffmpeg, "-loglevel", "quiet", "-i", filename, "-vcodec", 'libx264', '-acodec', 'aac ', '-f', 'mp4', "pipe:stdout"]
|
||||
|
||||
logger.debug(' '.join(ffmpeg_command))
|
||||
#logger.debug('command : %s', ffmpeg_command)
|
||||
#process = subprocess.Popen(ffmpeg_command, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, bufsize = -1)
|
||||
process = subprocess.Popen(ffmpeg_command, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, encoding='utf8')
|
||||
global process_list
|
||||
process_list.append(process)
|
||||
while True:
|
||||
#if time.time() - startTime > 120:
|
||||
# break
|
||||
line = process.stdout.read(1024)
|
||||
buffer.append(line)
|
||||
if sentBurst is False and time.time() > startTime + 1 and len(buffer) > 0:
|
||||
sentBurst = True
|
||||
for i in range(0, len(buffer) - 2):
|
||||
yield buffer.pop(0)
|
||||
elif time.time() > startTime + 1 and len(buffer) > 0:
|
||||
yield buffer.pop(0)
|
||||
process.poll()
|
||||
if isinstance(process.returncode, int):
|
||||
if process.returncode > 0:
|
||||
logger.debug('FFmpeg Error :%s', process.returncode)
|
||||
break
|
||||
|
||||
if process is not None and process.poll() is None:
|
||||
process.kill()
|
||||
return Response(stream_with_context(generate()), mimetype = "video/MP2T")
|
||||
|
||||
|
||||
|
||||
def get_video_info(filepath):
|
||||
try:
|
||||
from system.logic_command import SystemLogicCommand
|
||||
command = ['ffprobe', '-v', 'error', '-print_format', 'json', '-show_format', '-show_streams', "%s" % filepath]
|
||||
ret = SystemLogicCommand.execute_command_return(command, format='json')
|
||||
return ret
|
||||
except Exception as exception:
|
||||
logger.error('Exception:%s', exception)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
"""
|
||||
ffmpeg version 3.4.8-0ubuntu0.2 Copyright (c) 2000-2020 the FFmpeg developers
|
||||
built with gcc 7 (Ubuntu 7.5.0-3ubuntu1~18.04)
|
||||
configuration: --prefix=/usr --extra-version=0ubuntu0.2 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --enable-gpl --disable-stripping --enable-avresample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librubberband --enable-librsvg --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libopencv --enable-libx264 --enable-shared
|
||||
libavutil 55. 78.100 / 55. 78.100
|
||||
libavcodec 57.107.100 / 57.107.100
|
||||
libavformat 57. 83.100 / 57. 83.100
|
||||
libavdevice 57. 10.100 / 57. 10.100
|
||||
libavfilter 6.107.100 / 6.107.100
|
||||
libavresample 3. 7. 0 / 3. 7. 0
|
||||
libswscale 4. 8.100 / 4. 8.100
|
||||
libswresample 2. 9.100 / 2. 9.100
|
||||
libpostproc 54. 7.100 / 54. 7.100
|
||||
Hyper fast Audio and Video encoder
|
||||
usage: ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...
|
||||
"""
|
||||
39
plugin/ffmpeg/templates/ffmpeg_download.html
Executable file
39
plugin/ffmpeg/templates/ffmpeg_download.html
Executable file
@@ -0,0 +1,39 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<div>
|
||||
{{ macros.setting_input_text('url', 'URL', placeholder='http:// 주소', desc='비디오 파일 주소 or m3u8 주소') }}
|
||||
{{ macros.setting_input_text('filename', '파일명', value=arg['temp_filename']) }}
|
||||
{{ macros.setting_button([['download_start', '다운로드']]) }}
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var package_name = "{{arg['package_name'] }}";
|
||||
|
||||
$(document).ready(function(){
|
||||
});
|
||||
|
||||
//다운로드시작
|
||||
$("#download_start").click(function(e) {
|
||||
e.preventDefault();
|
||||
if ($("#url").val().startsWith('http') == false) {
|
||||
$.notify('<strong>URL을 입력하세요.</strong>', {
|
||||
type: 'warning'
|
||||
});
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
url: '/ffmpeg/ajax/download',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data: {url: $("#url").val(), filename: $("#filename").val()},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
$.notify('<strong>분석중..</strong>', {
|
||||
type: 'info'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
237
plugin/ffmpeg/templates/ffmpeg_list.html
Executable file
237
plugin/ffmpeg/templates/ffmpeg_list.html
Executable file
@@ -0,0 +1,237 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<!--<div class="d-inline-block"></div>-->
|
||||
<!--<div id="accordion">-->
|
||||
<style>
|
||||
.row>div { padding-top: 3px; padding-bottom:3px; }
|
||||
.row { align-items: center; word-break:break-all;}
|
||||
.row>div:nth-child(odd) { align-items: right; text-align: right; }
|
||||
.row>div:nth-child(even) { align-items: left; text-align: left; }
|
||||
</style>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.6/socket.io.js"></script>
|
||||
<table id="result_table" class="table table-sm tableRowHover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:5%">IDX</th>
|
||||
<th style="width:8%">Plugin</th>
|
||||
<th style="width:10%">시작시간</th>
|
||||
<th style="width:20%">파일명</th>
|
||||
<th style="width:8%">상태</th>
|
||||
<th style="width:15%">진행률</th>
|
||||
<th style="width:5%">길이</th>
|
||||
<th style="width:5%">PF</th>
|
||||
<th style="width:8%">배속</th>
|
||||
<th style="width:8%">진행시간</th>
|
||||
<th style="width:8%">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="list" >
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
|
||||
$(document).ready(function(){
|
||||
var package_name = 'ffmpeg';
|
||||
var protocol = window.location.protocol;
|
||||
var socket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/" + package_name)
|
||||
//var socket = io.connect("http://" + document.domain + ":" + location.port + "/ffmpeg", {transports:['websocket']}, {'force new connection': true});
|
||||
|
||||
//socket.emit("start");
|
||||
|
||||
|
||||
socket.on('add', function(data){
|
||||
str = make_item(data)
|
||||
document.getElementById("list").innerHTML += str;
|
||||
//document.getElementById("log_"+data.idx).scrollTop = document.getElementById("log_"+data.idx).scrollHeight ;
|
||||
});
|
||||
|
||||
// 화면을 켜놓고, 스케쥴러에 의해 시작될때 add전에 로그가온다.
|
||||
/*
|
||||
socket.on('log', function(data){
|
||||
if (document.getElementById("log_"+data.idx) != null) {
|
||||
document.getElementById("log_"+data.idx).innerHTML += data.line;
|
||||
document.getElementById("log_"+data.idx).scrollTop = document.getElementById("log_"+data.idx).scrollHeight;
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
|
||||
|
||||
socket.on('status_change', function(data) {
|
||||
button_html(data);
|
||||
});
|
||||
|
||||
socket.on('status', function(data){
|
||||
status_html(data);
|
||||
});
|
||||
|
||||
socket.on('last', function(data){
|
||||
status_html(data);
|
||||
button_html(data);
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: '/' + package_name + '/ajax/list',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
document.getElementById("list").innerHTML = '';
|
||||
str = ''
|
||||
for(i in data) {
|
||||
str += make_item(data[i])
|
||||
}
|
||||
document.getElementById("list").innerHTML = str;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
$("body").on('click', '#stop', function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
var idx = $(this).data('idx')
|
||||
$.ajax({
|
||||
url: '/ffmpeg/ajax/stop',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{ idx : idx},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on('click', '#play', function(e){
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var idx = $(this).data('idx')
|
||||
$.ajax({
|
||||
url: '/ffmpeg/ajax/play',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{ idx : idx},
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
console.log(data);
|
||||
if (data.startsWith('/')) {
|
||||
var url = '/open_file' + data;
|
||||
} else {
|
||||
var url = '/open_file/' + data;
|
||||
}
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function make_item(data) {
|
||||
//if (document.getElementById("log_"+data.idx) != null) return;
|
||||
str = '<tr style="cursor: pointer;" data-toggle="collapse" data-target="#collapse_'+ data.idx + '" aria-expanded="true" >';
|
||||
str += '<td scope="col" style="width:5%">'+ data.idx + '</td>';
|
||||
str += '<td scope="col" style="width:8%">'+ data.call_plugin + '</td>';
|
||||
str += '<td scope="col" style="width:10%">'+ data.start_time + '</td>';
|
||||
str += '<td scope="col" style="width:20%">'+ data.filename + '</td>';
|
||||
str += '<td id="status_'+data.idx+'" scope="col" style="width:8%">'+ data.status_kor + '</td>';
|
||||
var visi = 'hidden';
|
||||
if (parseInt(data.percent) > 0) {
|
||||
visi = 'visible';
|
||||
}
|
||||
str += '<td scope="col" style="width:20%"><div class="progress"><div id="progress_'+data.idx+'" class="progress-bar" style="visibility: '+visi+'; width:'+data.percent+'%">'+data.percent +'%</div></div></td>';
|
||||
str += '<td scope="col" style="width:5%">'+ data.duration_str + '</td>';
|
||||
str += '<td id="current_pf_count_'+data.idx+'" scope="col" style="width:5%">'+ data.current_pf_count + '</td>';
|
||||
str += '<td id="current_speed_'+data.idx+'" scope="col" style="width:8%">'+ data.current_speed + '</td>';
|
||||
str += '<td id="download_time_'+data.idx+'" scope="col" style="width:8%">'+ data.download_time + '</td>';
|
||||
str += '<td id="button_'+data.idx+'" scope="col" style="width:8%" class="tableRowHoverOff">';
|
||||
if (data.status_str == 'DOWNLOADING') {
|
||||
str += '<button id="stop" class="align-middle btn btn-outline-danger btn-sm" data-idx="'+data.idx+'">중지</button>';
|
||||
} else if (data.status_str == 'COMPLETED' && data.exist) {
|
||||
str += '<button id="play" class="align-middle btn btn-outline-info btn-sm" data-idx="'+data.idx+'">재생</button>';
|
||||
}
|
||||
str += '</td>'
|
||||
str += '</tr>'
|
||||
str += '<tr class="collapse tableRowHoverOff" style="cursor: pointer;" id="collapse_' + data.idx + '">';
|
||||
str += '<td colspan="11">';
|
||||
str += '<div id="detail_'+data.idx+'">';
|
||||
str += get_detail(data);
|
||||
str += '</div>';
|
||||
/*
|
||||
str += '<div><textarea class="form-control" id="log_'+data.idx+'" rows="20">';
|
||||
for (var i = 0; i < data.log.length; i++) {
|
||||
str += data.log[i] + '\n';
|
||||
}
|
||||
str += '</textarea></div>';
|
||||
*/
|
||||
str += '</td>';
|
||||
str += '</tr>'
|
||||
return str
|
||||
}
|
||||
|
||||
function info_html($left, $right) {
|
||||
var str = '<div class="row">' +
|
||||
'<div class="col-sm-2">' +
|
||||
'<strong>'+ $left +'</strong>' +
|
||||
'</div>' +
|
||||
'<div class="col-sm-10">' +
|
||||
'<div class="input-group col-sm-9">' +
|
||||
'<span class="text-left" style="padding-left:10px; padding-top:3px">';
|
||||
if ($left == 'URL') {
|
||||
str += '<a href="/hls?url=' + $right + '" target="_blank">';
|
||||
}
|
||||
str += $right;
|
||||
if ($left == 'URL') {
|
||||
str += '</a>';
|
||||
}
|
||||
str += '</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
return str;
|
||||
}
|
||||
|
||||
function get_detail(data) {
|
||||
var str = info_html('URL', data.url);
|
||||
str += info_html('임시경로', data.temp_fullpath);
|
||||
str += info_html('저장경로', data.save_fullpath);
|
||||
str += info_html('진행률(current/total)', data.percent+ '% (' + data.current_duration + ' / ' + data.duration + ')');
|
||||
str += info_html('현재 비트레이트', data.current_bitrate);
|
||||
str += info_html('종료시간', data.end_time);
|
||||
str += info_html('허용 Packet Fail 수', data.max_pf_count);
|
||||
str += info_html('파일 Exist', data.exist);
|
||||
if (data.status_str == 'COMPLETED') {
|
||||
str += info_html('파일 크기', data.filesize_str);
|
||||
str += info_html('다운 속도', data.download_speed);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function button_html(data) {
|
||||
if (data.status_str == 'DOWNLOADING') {
|
||||
document.getElementById("button_" + data.idx).innerHTML =
|
||||
'<button id="stop" class="align-middle btn btn-outline-danger btn-sm" data-idx="'+data.idx+'">중지</button>';
|
||||
} else if (data.status_str == 'COMPLETED' && data.exist) {
|
||||
document.getElementById("button_" + data.idx).innerHTML = '<button id="play" class="align-middle btn btn-outline-info btn-sm" data-idx="'+data.idx+'">재생</button>';
|
||||
} else {
|
||||
document.getElementById("button_" + data.idx).innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
function status_html(data) {
|
||||
var progress = document.getElementById("progress_" + data.idx);
|
||||
progress.style.width = data.percent+ '%';
|
||||
progress.innerHTML = data.percent+ '%';
|
||||
progress.style.visibility = 'visible';
|
||||
document.getElementById("status_" + data.idx).innerHTML = data.status_kor;
|
||||
document.getElementById("current_pf_count_" + data.idx).innerHTML = data.current_pf_count;
|
||||
document.getElementById("current_speed_" + data.idx).innerHTML = data.current_speed;
|
||||
document.getElementById("download_time_" + data.idx).innerHTML = data.download_time;
|
||||
//document.getElementById("log").innerHTML += str + '\r\n';
|
||||
document.getElementById("detail_" + data.idx).innerHTML = get_detail(data);
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
79
plugin/ffmpeg/templates/ffmpeg_setting.html
Executable file
79
plugin/ffmpeg/templates/ffmpeg_setting.html
Executable file
@@ -0,0 +1,79 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
|
||||
<div>
|
||||
{{ macros.m_button_group([['global_setting_save_btn', '설정 저장']])}}
|
||||
{{ macros.m_row_start('5') }}
|
||||
{{ macros.m_row_end() }}
|
||||
<nav>
|
||||
{{ macros.m_tab_head_start() }}
|
||||
{{ macros.m_tab_head2('normal', '기본', true) }}
|
||||
{{ macros.m_tab_head_end() }}
|
||||
</nav >
|
||||
<form id='setting' name='setting'>
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
{{ macros.m_tab_content_start('normal', true) }}
|
||||
{{ macros.setting_input_text_and_buttons('ffmpeg_path', 'FFMPEG 경로', [['ffmpeg_version', '버전확인'], ['select_binary_path_btn', '파일 선택']], value=arg['ffmpeg_path']) }}
|
||||
{{ macros.setting_input_text_and_buttons('temp_path', '임시 폴더', [['select_temp_path_btn', '경로 선택']], value=arg['temp_path'], desc=['다운로드 파일이 임시로 저장될 폴더 입니다.']) }}
|
||||
|
||||
{{ macros.setting_input_text_and_buttons('save_path', '저장 폴더', [['select_save_path_btn', '경로 선택']], value=arg['save_path'], placeholder='저장 폴더 경로', desc='정상적으로 완료된 파일이 이동할 폴더 입니다.') }}
|
||||
{{ macros.setting_input_int('max_pf_count', '허용 Packet Fail 수', value=arg['max_pf_count'], min='0', placeholder='0', desc=['이 값보다 Packet Fail 횟수가 더 많으면 실패처리 합니다.', '0일 경우 Packet Fail이 발생하면 바로 실패처리.']) }}
|
||||
|
||||
{{ macros.setting_checkbox('if_fail_remove_tmp_file', '임시 파일', arg['if_fail_remove_tmp_file'], desc='On : 실패시 임시 파일 자동 삭제') }}
|
||||
{{ macros.setting_input_int('timeout_minute', '타임아웃 시간', value=arg['timeout_minute'], desc=['이 시간 안에 완료가 되지 않으면 시간초과 에러를 발생합니다.', '속도가 느린 경우 값을 올려 설정하세요. 분 단위']) }}
|
||||
{{ macros.m_tab_content_end() }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var package_name = "{{arg['package_name'] }}";
|
||||
|
||||
$(document).ready(function(){
|
||||
});
|
||||
|
||||
//버전
|
||||
$("#ffmpeg_version").click(function(e) {
|
||||
e.preventDefault();
|
||||
$.ajax({
|
||||
url: '/ffmpeg/ajax/ffmpeg_version',
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data:{ },
|
||||
dataType: "json",
|
||||
success: function (list) {
|
||||
var str = '';
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
str += "<div>" + list[i] + "</div>";
|
||||
}
|
||||
document.getElementById("modal_title").innerHTML = "ffmpeg -version";
|
||||
document.getElementById("modal_body").innerHTML = str;
|
||||
$("#large_modal").modal();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$("body").on('click', '#select_temp_path_btn', function(e){
|
||||
e.preventDefault();
|
||||
m_select_local_file_modal("임시 저장 경로 선택", $('#temp_path').val().trim(), true, function(result){
|
||||
document.getElementById("temp_path").value = result;
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on('click', '#select_save_path_btn', function(e){
|
||||
e.preventDefault();
|
||||
m_select_local_file_modal("저장 경로 선택", $('#save_path').val().trim(), true, function(result){
|
||||
document.getElementById("save_path").value = result;
|
||||
});
|
||||
});
|
||||
|
||||
$("body").on('click', '#select_binary_path_btn', function(e){
|
||||
e.preventDefault();
|
||||
m_select_local_file_modal("실행 파일 선택", '/', false, function(result){
|
||||
document.getElementById("ffmpeg_path").value = result;
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user