썸네일 다운로드 기능(+API) 추가
This commit is contained in:
30
README.md
30
README.md
@@ -58,7 +58,7 @@ API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로
|
|||||||
그리고 만약 주소가 플레이리스트라면 `_type` 키에 `"playlist"`라는 값이 들어 있습니다. 이때는 `entries` 키에 리스트가 들어있어 동영상들의 제목과 ID를 확인할 수 있습니다.
|
그리고 만약 주소가 플레이리스트라면 `_type` 키에 `"playlist"`라는 값이 들어 있습니다. 이때는 `entries` 키에 리스트가 들어있어 동영상들의 제목과 ID를 확인할 수 있습니다.
|
||||||
|
|
||||||
### /youtube-dl/api/download
|
### /youtube-dl/api/download
|
||||||
다운로드 준비를 요청하는 API
|
비디오 다운로드 준비를 요청하는 API
|
||||||
#### Request
|
#### Request
|
||||||
키 | 설명 | 필수 | 타입
|
키 | 설명 | 필수 | 타입
|
||||||
--- | --- | --- | ---
|
--- | --- | --- | ---
|
||||||
@@ -83,6 +83,29 @@ API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로
|
|||||||
`errorCode` | 에러 코드 | Integer
|
`errorCode` | 에러 코드 | Integer
|
||||||
`index` | 동영상 인덱스. 이후 다운로드를 제어할 때 이 값이 필요함 | Integer
|
`index` | 동영상 인덱스. 이후 다운로드를 제어할 때 이 값이 필요함 | Integer
|
||||||
|
|
||||||
|
### /youtube-dl/api/thumbnail
|
||||||
|
썸네일 다운로드 준비를 요청하는 API
|
||||||
|
#### Request
|
||||||
|
키 | 설명 | 필수 | 타입
|
||||||
|
--- | --- | --- | ---
|
||||||
|
`plugin` | 플러그인 이름 | O | String
|
||||||
|
`key` | 임의의 키. 이후 다운로드를 제어할 때 이 키가 필요함 | O | String
|
||||||
|
`url` | 동영상 주소 | O | String
|
||||||
|
`filename` | 파일명. 템플릿 규칙은 https://github.com/ytdl-org/youtube-dl/#output-template 참고. 미지정 시 사용자 설정 | X | String
|
||||||
|
`save_path` | 저장 폴더 경로. 미지정 시 사용자 설정 | X | String
|
||||||
|
`all_thumbnails` | 모든 썸네일 다운로드 여부. `false`는 대표 썸네일 하나만 다운로드. 기본값: `false` | X | Boolean
|
||||||
|
`dateafter` | 지정한 날짜 이후에 업로드된 동영상만 다운로드. 미지정 시 모든 동영상 다운로드 | X | String
|
||||||
|
`archive` | 다운로드한 동영상의 ID를 기록할 파일 경로. 파일이 이미 있으면 이미 다운로드한 동영상은 다운로드 하지 않음. 미지정 시 기록하지 않음 | X | String
|
||||||
|
`start` | 다운로드 준비 후 바로 다운로드를 시작할지 여부. 기본값: `false` | X | Boolean
|
||||||
|
`cookiefile` | 다운로드 시 필요한 쿠키 파일 경로 | X | String
|
||||||
|
|
||||||
|
`dateafter` 키에 넣을 수 있는 날짜는 `YYYYMMDD` 또는 `(now|today)[+-][0-9](day|week|month|year)(s)?` 형식의 문자열입니다.
|
||||||
|
#### Response
|
||||||
|
키 | 설명 | 타입
|
||||||
|
--- | --- | ---
|
||||||
|
`errorCode` | 에러 코드 | Integer
|
||||||
|
`index` | 동영상 인덱스. 이후 다운로드를 제어할 때 이 값이 필요함 | Integer
|
||||||
|
|
||||||
### /youtube-dl/api/start
|
### /youtube-dl/api/start
|
||||||
다운로드 시작을 요청하는 API
|
다운로드 시작을 요청하는 API
|
||||||
#### Request
|
#### Request
|
||||||
@@ -124,16 +147,19 @@ API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로
|
|||||||
--- | --- | ---
|
--- | --- | ---
|
||||||
`errorCode` | 에러 코드 | Integer
|
`errorCode` | 에러 코드 | Integer
|
||||||
`status` | 요청을 받았을 당시의 상태 | Status
|
`status` | 요청을 받았을 당시의 상태 | Status
|
||||||
|
`type` | 다운로드 타입. `"video" "thumbnail"` | String
|
||||||
`start_time` | 다운로드 시작 시간 | String
|
`start_time` | 다운로드 시작 시간 | String
|
||||||
`end_time` | 다운로드 종료 시간 | String
|
`end_time` | 다운로드 종료 시간 | String
|
||||||
`temp_path` | 임시 폴더 경로 | String
|
`temp_path` | 임시 폴더 경로 | String
|
||||||
`save_path` | 저장 폴더 경로 | String
|
`save_path` | 저장 폴더 경로 | String
|
||||||
|
|
||||||
`start_time` 키와 `end_time` 키에 들어있는 시간은 "YYYY-MM-DDThh:mm:ss" 형식의 문자열입니다.
|
`start_time` 키와 `end_time` 키에 들어있는 시간은 `YYYY-MM-DDThh:mm:ss` 형식의 문자열입니다.
|
||||||
물론 해당 정보가 없으면 null입니다.
|
물론 해당 정보가 없으면 null입니다.
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
v2.4.0
|
v2.4.0
|
||||||
|
* 썸네일 다운로드 기능 추가
|
||||||
|
* thumbnail API 추가
|
||||||
* 설정에 경로 선택 버튼 추가
|
* 설정에 경로 선택 버튼 추가
|
||||||
|
|
||||||
v2.3.1
|
v2.3.1
|
||||||
|
|||||||
@@ -118,7 +118,42 @@ class LogicNormal(object):
|
|||||||
if 'cookiefile' in kwagrs and kwagrs['cookiefile']:
|
if 'cookiefile' in kwagrs and kwagrs['cookiefile']:
|
||||||
opts['cookiefile'] = kwagrs['cookiefile']
|
opts['cookiefile'] = kwagrs['cookiefile']
|
||||||
dateafter = kwagrs.get('dateafter')
|
dateafter = kwagrs.get('dateafter')
|
||||||
youtube_dl = MyYoutubeDL(plugin, url, filename, temp_path, save_path, opts, dateafter)
|
youtube_dl = MyYoutubeDL(plugin, 'video', url, filename, temp_path, save_path, opts, dateafter)
|
||||||
|
youtube_dl.key = kwagrs.get('key')
|
||||||
|
LogicNormal.youtube_dl_list.append(youtube_dl) # 리스트 추가
|
||||||
|
return youtube_dl
|
||||||
|
except Exception as e:
|
||||||
|
logger.error('Exception:%s', e)
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def thumbnail(**kwagrs):
|
||||||
|
try:
|
||||||
|
logger.debug(kwagrs)
|
||||||
|
plugin = kwagrs['plugin']
|
||||||
|
url = kwagrs['url']
|
||||||
|
filename = kwagrs['filename']
|
||||||
|
temp_path = kwagrs['temp_path']
|
||||||
|
save_path = kwagrs['save_path']
|
||||||
|
opts = {
|
||||||
|
'skip_download': True
|
||||||
|
}
|
||||||
|
if 'all_thumbnails' in kwagrs and str(kwagrs['all_thumbnails']).lower() != 'false':
|
||||||
|
opts['write_all_thumbnails'] = True
|
||||||
|
else:
|
||||||
|
opts['writethumbnail'] = True
|
||||||
|
|
||||||
|
if 'archive' in kwagrs and kwagrs['archive']:
|
||||||
|
opts['download_archive'] = kwagrs['archive']
|
||||||
|
if 'proxy' in kwagrs and kwagrs['proxy']:
|
||||||
|
opts['proxy'] = kwagrs['proxy']
|
||||||
|
if 'ffmpeg_path' in kwagrs and kwagrs['ffmpeg_path']:
|
||||||
|
opts['ffmpeg_location'] = kwagrs['ffmpeg_path']
|
||||||
|
if 'cookiefile' in kwagrs and kwagrs['cookiefile']:
|
||||||
|
opts['cookiefile'] = kwagrs['cookiefile']
|
||||||
|
dateafter = kwagrs.get('dateafter')
|
||||||
|
youtube_dl = MyYoutubeDL(plugin, 'thumbnail', url, filename, temp_path, save_path, opts, dateafter)
|
||||||
youtube_dl.key = kwagrs.get('key')
|
youtube_dl.key = kwagrs.get('key')
|
||||||
LogicNormal.youtube_dl_list.append(youtube_dl) # 리스트 추가
|
LogicNormal.youtube_dl_list.append(youtube_dl) # 리스트 추가
|
||||||
return youtube_dl
|
return youtube_dl
|
||||||
@@ -140,8 +175,8 @@ class LogicNormal(object):
|
|||||||
data['status_str'] = youtube_dl.status.name
|
data['status_str'] = youtube_dl.status.name
|
||||||
data['status_ko'] = str(youtube_dl.status)
|
data['status_ko'] = str(youtube_dl.status)
|
||||||
data['end_time'] = ''
|
data['end_time'] = ''
|
||||||
data['extractor'] = youtube_dl.info_dict['extractor'] if \
|
data['extractor'] = youtube_dl.type + (
|
||||||
youtube_dl.info_dict['extractor'] is not None else ''
|
' - ' + youtube_dl.info_dict['extractor'] if youtube_dl.info_dict['extractor'] is not None else '')
|
||||||
data['title'] = youtube_dl.info_dict['title'] if \
|
data['title'] = youtube_dl.info_dict['title'] if \
|
||||||
youtube_dl.info_dict['title'] is not None else youtube_dl.url
|
youtube_dl.info_dict['title'] is not None else youtube_dl.url
|
||||||
data['uploader'] = youtube_dl.info_dict['uploader'] if youtube_dl.info_dict['uploader'] is not None else ''
|
data['uploader'] = youtube_dl.info_dict['uploader'] if youtube_dl.info_dict['uploader'] is not None else ''
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ class MyYoutubeDL(object):
|
|||||||
__index = 0
|
__index = 0
|
||||||
_last_msg = ''
|
_last_msg = ''
|
||||||
|
|
||||||
def __init__(self, plugin, url, filename, temp_path, save_path=None, opts=None, dateafter=None, datebefore=None):
|
def __init__(self, plugin, type_name, url, filename, temp_path, save_path=None, opts=None, dateafter=None,
|
||||||
|
datebefore=None):
|
||||||
# from youtube_dl.utils import DateRange
|
# from youtube_dl.utils import DateRange
|
||||||
from .plugin import YOUTUBE_DL_PACKAGE
|
from .plugin import YOUTUBE_DL_PACKAGE
|
||||||
DateRange = __import__('%s.utils' % YOUTUBE_DL_PACKAGE, fromlist=['DateRange']).DateRange
|
DateRange = __import__('%s.utils' % YOUTUBE_DL_PACKAGE, fromlist=['DateRange']).DateRange
|
||||||
@@ -53,6 +54,7 @@ class MyYoutubeDL(object):
|
|||||||
if opts is None:
|
if opts is None:
|
||||||
opts = {}
|
opts = {}
|
||||||
self.plugin = plugin
|
self.plugin = plugin
|
||||||
|
self.type = type_name
|
||||||
self.url = url
|
self.url = url
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
if not os.path.isdir(temp_path):
|
if not os.path.isdir(temp_path):
|
||||||
@@ -166,6 +168,7 @@ class MyYoutubeDL(object):
|
|||||||
# import youtube_dl
|
# import youtube_dl
|
||||||
from .plugin import YOUTUBE_DL_PACKAGE
|
from .plugin import YOUTUBE_DL_PACKAGE
|
||||||
youtube_dl = __import__('%s' % YOUTUBE_DL_PACKAGE)
|
youtube_dl = __import__('%s' % YOUTUBE_DL_PACKAGE)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ydl_opts = {
|
ydl_opts = {
|
||||||
'simulate': True,
|
'simulate': True,
|
||||||
|
|||||||
65
plugin.py
65
plugin.py
@@ -41,7 +41,7 @@ if ModelSetting.get_bool('activate_cors'):
|
|||||||
menu = {
|
menu = {
|
||||||
'main': [package_name, 'youtube-dl'],
|
'main': [package_name, 'youtube-dl'],
|
||||||
'sub': [
|
'sub': [
|
||||||
['setting', '설정'], ['download', '다운로드'], ['list', '목록'], ['log', '로그']
|
['setting', '설정'], ['download', '다운로드'], ['thumbnail', '썸네일 다운로드'], ['list', '목록'], ['log', '로그']
|
||||||
],
|
],
|
||||||
'category': 'vod'
|
'category': 'vod'
|
||||||
}
|
}
|
||||||
@@ -98,6 +98,11 @@ def first_menu(sub):
|
|||||||
arg['postprocessor_list'] = LogicNormal.get_postprocessor_list()
|
arg['postprocessor_list'] = LogicNormal.get_postprocessor_list()
|
||||||
return render_template('%s_%s.html' % (package_name, sub), arg=arg)
|
return render_template('%s_%s.html' % (package_name, sub), arg=arg)
|
||||||
|
|
||||||
|
elif sub == 'thumbnail':
|
||||||
|
default_filename = ModelSetting.get('default_filename')
|
||||||
|
arg['filename'] = default_filename if default_filename else LogicNormal.get_default_filename()
|
||||||
|
return render_template('%s_%s.html' % (package_name, sub), arg=arg)
|
||||||
|
|
||||||
elif sub == 'list':
|
elif sub == 'list':
|
||||||
return render_template('%s_%s.html' % (package_name, sub), arg=arg)
|
return render_template('%s_%s.html' % (package_name, sub), arg=arg)
|
||||||
|
|
||||||
@@ -157,6 +162,19 @@ def ajax(sub):
|
|||||||
socketio_emit('add', youtube_dl)
|
socketio_emit('add', youtube_dl)
|
||||||
return jsonify([])
|
return jsonify([])
|
||||||
|
|
||||||
|
elif sub == 'thumbnail':
|
||||||
|
youtube_dl = LogicNormal.thumbnail(plugin=package_name,
|
||||||
|
url=request.form['url'],
|
||||||
|
filename=request.form['filename'],
|
||||||
|
temp_path=ModelSetting.get('temp_path'),
|
||||||
|
save_path=ModelSetting.get('save_path'),
|
||||||
|
all_thumbnails=request.form['all_thumbnails'],
|
||||||
|
proxy=ModelSetting.get('proxy'),
|
||||||
|
ffmpeg_path=ModelSetting.get('ffmpeg_path'))
|
||||||
|
youtube_dl.start()
|
||||||
|
socketio_emit('add', youtube_dl)
|
||||||
|
return jsonify([])
|
||||||
|
|
||||||
elif sub == 'list':
|
elif sub == 'list':
|
||||||
ret = []
|
ret = []
|
||||||
for i in LogicNormal.youtube_dl_list:
|
for i in LogicNormal.youtube_dl_list:
|
||||||
@@ -208,7 +226,7 @@ def api(sub):
|
|||||||
ret['info_dict'] = info_dict
|
ret['info_dict'] = info_dict
|
||||||
return jsonify(ret)
|
return jsonify(ret)
|
||||||
|
|
||||||
# 다운로드 준비를 요청하는 API
|
# 비디오 다운로드 준비를 요청하는 API
|
||||||
elif sub == 'download':
|
elif sub == 'download':
|
||||||
key = request.values.get('key')
|
key = request.values.get('key')
|
||||||
url = request.values.get('url')
|
url = request.values.get('url')
|
||||||
@@ -257,6 +275,47 @@ def api(sub):
|
|||||||
socketio_emit('add', youtube_dl)
|
socketio_emit('add', youtube_dl)
|
||||||
return jsonify(ret)
|
return jsonify(ret)
|
||||||
|
|
||||||
|
# 썸네일 다운로드 준비를 요청하는 API
|
||||||
|
elif sub == 'thumbnail':
|
||||||
|
key = request.values.get('key')
|
||||||
|
url = request.values.get('url')
|
||||||
|
filename = request.values.get('filename', ModelSetting.get('default_filename'))
|
||||||
|
save_path = request.values.get('save_path', ModelSetting.get('save_path'))
|
||||||
|
all_thumbnails = request.values.get('all_thumbnails', False)
|
||||||
|
dateafter = request.values.get('dateafter', None)
|
||||||
|
archive = request.values.get('archive', None)
|
||||||
|
start = request.values.get('start', False)
|
||||||
|
cookiefile = request.values.get('cookiefile', None)
|
||||||
|
ret = {
|
||||||
|
'errorCode': 0,
|
||||||
|
'index': None
|
||||||
|
}
|
||||||
|
if None in (key, url):
|
||||||
|
return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
|
||||||
|
if not url.startswith('http'):
|
||||||
|
return LogicNormal.abort(ret, 2) # 잘못된 동영상 주소
|
||||||
|
if not filename:
|
||||||
|
filename = LogicNormal.get_default_filename()
|
||||||
|
youtube_dl = LogicNormal.thumbnail(plugin=plugin,
|
||||||
|
url=url,
|
||||||
|
filename=filename,
|
||||||
|
temp_path=ModelSetting.get('temp_path'),
|
||||||
|
save_path=save_path,
|
||||||
|
all_thumbnails=all_thumbnails,
|
||||||
|
dateafter=dateafter,
|
||||||
|
archive=archive,
|
||||||
|
proxy=ModelSetting.get('proxy'),
|
||||||
|
ffmpeg_path=ModelSetting.get('ffmpeg_path'),
|
||||||
|
key=key,
|
||||||
|
cookiefile=cookiefile)
|
||||||
|
if youtube_dl is None:
|
||||||
|
return LogicNormal.abort(ret, 10) # 실패
|
||||||
|
ret['index'] = youtube_dl.index
|
||||||
|
if start:
|
||||||
|
youtube_dl.start()
|
||||||
|
socketio_emit('add', youtube_dl)
|
||||||
|
return jsonify(ret)
|
||||||
|
|
||||||
# 다운로드 시작을 요청하는 API
|
# 다운로드 시작을 요청하는 API
|
||||||
elif sub == 'start':
|
elif sub == 'start':
|
||||||
index = request.values.get('index')
|
index = request.values.get('index')
|
||||||
@@ -306,6 +365,7 @@ def api(sub):
|
|||||||
ret = {
|
ret = {
|
||||||
'errorCode': 0,
|
'errorCode': 0,
|
||||||
'status': None,
|
'status': None,
|
||||||
|
'type': None,
|
||||||
'start_time': None,
|
'start_time': None,
|
||||||
'end_time': None,
|
'end_time': None,
|
||||||
'temp_path': None,
|
'temp_path': None,
|
||||||
@@ -320,6 +380,7 @@ def api(sub):
|
|||||||
if youtube_dl.key != key:
|
if youtube_dl.key != key:
|
||||||
return LogicNormal.abort(ret, 4) # 키가 일치하지 않음
|
return LogicNormal.abort(ret, 4) # 키가 일치하지 않음
|
||||||
ret['status'] = youtube_dl.status.name
|
ret['status'] = youtube_dl.status.name
|
||||||
|
ret['type'] = youtube_dl.type
|
||||||
ret['start_time'] = youtube_dl.start_time.strftime('%Y-%m-%dT%H:%M:%S') if \
|
ret['start_time'] = youtube_dl.start_time.strftime('%Y-%m-%dT%H:%M:%S') if \
|
||||||
youtube_dl.start_time is not None else None
|
youtube_dl.start_time is not None else None
|
||||||
ret['end_time'] = youtube_dl.end_time.strftime('%Y-%m-%dT%H:%M:%S') if \
|
ret['end_time'] = youtube_dl.end_time.strftime('%Y-%m-%dT%H:%M:%S') if \
|
||||||
|
|||||||
26
static/youtube-dl_thumbnail.js
Normal file
26
static/youtube-dl_thumbnail.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const url = document.getElementById('url');
|
||||||
|
const download_btn = document.getElementById('download_btn');
|
||||||
|
|
||||||
|
// 다운로드
|
||||||
|
download_btn.addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!url.value.startsWith('http')) {
|
||||||
|
notify('URL을 입력하세요.', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`/${package_name}/ajax/thumbnail`, {
|
||||||
|
method: 'POST',
|
||||||
|
cache: 'no-cache',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||||
|
},
|
||||||
|
body: get_formdata('#download')
|
||||||
|
}).then(response => response.json()).then(() => {
|
||||||
|
notify('분석중..', 'info');
|
||||||
|
}).catch(() => {
|
||||||
|
notify('다운로드 요청 실패', 'danger');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -11,13 +11,13 @@
|
|||||||
<table class="table table-sm tableRowHover">
|
<table class="table table-sm tableRowHover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 5%">IDX</th>
|
<th style="width: 4%">IDX</th>
|
||||||
<th style="width: 8%">Plugin</th>
|
<th style="width: 8%">Plugin</th>
|
||||||
<th style="width: 10%">시작시간</th>
|
<th style="width: 10%">시작시간</th>
|
||||||
<th style="width: 10%">타입</th>
|
<th style="width: 14%">타입</th>
|
||||||
<th style="width: 28%">제목</th>
|
<th style="width: 29%">제목</th>
|
||||||
<th style="width: 8%">상태</th>
|
<th style="width: 8%">상태</th>
|
||||||
<th style="width: 15%">진행률</th>
|
<th style="width: 11%">진행률</th>
|
||||||
<th style="width: 8%">진행시간</th>
|
<th style="width: 8%">진행시간</th>
|
||||||
<th style="width: 8%">Action</th>
|
<th style="width: 8%">Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
17
templates/youtube-dl_thumbnail.html
Normal file
17
templates/youtube-dl_thumbnail.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<form id="download">
|
||||||
|
{{ macros.setting_input_text('url', 'URL', placeholder='http:// 주소', desc='유튜브, 네이버TV 등 동영상 주소') }}
|
||||||
|
{{ macros.setting_input_text('filename', '파일명', value=arg['filename'], desc='템플릿 규칙은 https://github.com/ytdl-org/youtube-dl/#output-template 참고') }}
|
||||||
|
{{ macros.setting_checkbox('all_thumbnails', '모든 썸네일 다운로드', value='False') }}
|
||||||
|
{{ macros.setting_button([['download_btn', '다운로드']]) }}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
"use strict";
|
||||||
|
const package_name = '{{ arg["package_name"] }}';
|
||||||
|
</script>
|
||||||
|
<script src="{{ url_for('.static', filename='%s.js' % arg['template_name']) }}"></script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user