Compare commits
10 Commits
3202fd2fb9
...
1167e60c36
| Author | SHA1 | Date | |
|---|---|---|---|
| 1167e60c36 | |||
|
|
51ef1ee459 | ||
|
|
3410ddd73c | ||
|
|
26e4f96d49 | ||
|
|
3ef17ad0b3 | ||
|
|
44b792bc28 | ||
|
|
3d1e12d081 | ||
|
|
8cd3c769b0 | ||
|
|
28dcded7ce | ||
|
|
c74ad4bc78 |
22
README.md
22
README.md
@@ -15,7 +15,7 @@ SJVA에서 "시스템 → 플러그인 → 플러그인 수동 설치" 칸에
|
||||
API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로드를 요청할 수 있습니다.
|
||||
다른 플러그인이 멋대로 다운로드를 중지할 수 없도록 다운로드를 요청할 때 임의의 키를 넘겨받습니다. 중지 요청 시 키가 일치해야 요청이 실행됩니다.
|
||||
|
||||
[youtube-dl](https://github.com/ytdl-org/youtube-dl)의 DMCA 테이크다운 사태 이후, 비슷한 상황을 대비하기 위해 youtube-dl의 포크 프로젝트 [youtube-dlc](https://github.com/blackjack4494/yt-dlc)의 포크 프로젝트(...)인 [yt-dlp](https://github.com/pukkandan/yt-dlp)를 추가했습니다.
|
||||
[youtube-dl](https://github.com/ytdl-org/youtube-dl)의 DMCA 테이크다운 사태 이후, 비슷한 상황을 대비하기 위해 youtube-dl의 포크 프로젝트 [youtube-dlc](https://github.com/blackjack4494/yt-dlc)의 포크 프로젝트(...)인 [yt-dlp](https://github.com/yt-dlp/yt-dlp)를 추가했습니다.
|
||||
설정에서 취향껏 골라서 사용하시면 되며 youtube-dl가 우선 지원됩니다.
|
||||
|
||||
## API
|
||||
@@ -93,8 +93,10 @@ API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로
|
||||
| `archive` | 다운로드한 동영상의 ID를 기록할 파일 경로. 파일이 이미 있으면 이미 다운로드한 동영상은 다운로드 하지 않음. 미지정 시 기록하지 않음 | X | String |
|
||||
| `start` | 다운로드 준비 후 바로 다운로드를 시작할지 여부. 기본값: `false` | X | Boolean |
|
||||
| `cookiefile` | 다운로드 시 필요한 쿠키 파일 경로 | X | String |
|
||||
| `headers` | 다운로드 시 사용할 HTTP 헤더 정보 | X | String |
|
||||
|
||||
`dateafter` 키에 넣을 수 있는 날짜는 `"YYYYMMDD"` 또는 `"(now|today)[+-][0-9](day|week|month|year)(s)?"` 형식의 문자열입니다.
|
||||
`headers` 키에 넣는 값은 `json` 형식의 문자열입니다.
|
||||
|
||||
`playlist` 키에 넣을 수 있는 값은 `"1-3,7,10-13"` 형식의 범위 또는 `"reverse"`, `"random"`입니다.
|
||||
범위를 넣으면 플레이리스트에서 선택한 것만 다운로드합니다.
|
||||
@@ -127,8 +129,10 @@ API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로
|
||||
| `archive` | 다운로드한 동영상의 ID를 기록할 파일 경로. 파일이 이미 있으면 이미 다운로드한 동영상은 다운로드 하지 않음. 미지정 시 기록하지 않음 | X | String |
|
||||
| `start` | 다운로드 준비 후 바로 다운로드를 시작할지 여부. 기본값: `false` | X | Boolean |
|
||||
| `cookiefile` | 다운로드 시 필요한 쿠키 파일 경로 | X | String |
|
||||
| `headers` | 다운로드 시 사용할 HTTP 헤더 정보 | X | String |
|
||||
|
||||
`dateafter` 키에 넣을 수 있는 날짜는 `"YYYYMMDD"` 또는 `"(now|today)[+-][0-9](day|week|month|year)(s)?"` 형식의 문자열입니다.
|
||||
`headers` 키에 넣는 값은 `json` 형식의 문자열입니다.
|
||||
|
||||
`playlist` 키에 넣을 수 있는 값은 `"1-3,7,10-13"` 형식의 범위 또는 `"reverse"`, `"random"`입니다.
|
||||
범위를 넣으면 플레이리스트에서 선택한 것만 다운로드합니다.
|
||||
@@ -163,8 +167,10 @@ API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로
|
||||
| `archive` | 다운로드한 동영상의 ID를 기록할 파일 경로. 파일이 이미 있으면 이미 다운로드한 동영상은 다운로드 하지 않음. 미지정 시 기록하지 않음 | X | String |
|
||||
| `start` | 다운로드 준비 후 바로 다운로드를 시작할지 여부. 기본값: `false` | X | Boolean |
|
||||
| `cookiefile` | 다운로드 시 필요한 쿠키 파일 경로 | X | String |
|
||||
| `headers` | 다운로드 시 사용할 HTTP 헤더 정보 | X | String |
|
||||
|
||||
`dateafter` 키에 넣을 수 있는 날짜는 `"YYYYMMDD"` 또는 `"(now|today)[+-][0-9](day|week|month|year)(s)?"` 형식의 문자열입니다.
|
||||
`headers` 키에 넣는 값은 `json` 형식의 문자열입니다.
|
||||
|
||||
`playlist` 키에 넣을 수 있는 값은 `"1-3,7,10-13"` 형식의 범위 또는 `"reverse"`, `"random"`입니다.
|
||||
범위를 넣으면 플레이리스트에서 선택한 것만 다운로드합니다.
|
||||
@@ -245,6 +251,20 @@ API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로
|
||||
|
||||
## Changelog
|
||||
|
||||
v4.0.1
|
||||
|
||||
- 다운로드 요청 오류 수정
|
||||
|
||||
v4.0.0
|
||||
|
||||
- 최신 플러그인 구조로 변경
|
||||
- download, thumbnail, sub API에 headers 키 추가
|
||||
http 헤더 수정이 필요한 경우 활용할 수 있습니다.
|
||||
|
||||
v3.1.1
|
||||
|
||||
- 디폴트 youtube_dl 패키지를 yt-dlp로 변경
|
||||
|
||||
v3.1.0
|
||||
|
||||
- yt-dlp일 때 작동 안하는 문제 수정
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
from .plugin import blueprint, menu, plugin_info, plugin_load, plugin_unload
|
||||
|
||||
8
abort.py
Normal file
8
abort.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from flask import jsonify
|
||||
|
||||
|
||||
class LogicAbort:
|
||||
@staticmethod
|
||||
def abort(base, code):
|
||||
base["errorCode"] = code
|
||||
return jsonify(base)
|
||||
10
info.json
10
info.json
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"version": "3.1.0",
|
||||
"name": "youtube-dl",
|
||||
"category_name": "vod",
|
||||
"developer": "joyfuI",
|
||||
"description": "유튜브, 네이버TV 등 동영상 사이트에서 동영상 다운로드",
|
||||
"home": "https://github.com/joyfuI/youtube-dl",
|
||||
"more": "",
|
||||
"category": "vod"
|
||||
}
|
||||
7
info.yaml
Normal file
7
info.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
title: "유튜브 다운로더"
|
||||
version: "0.1.0"
|
||||
package_name: "youtube-dl"
|
||||
developer: "flaskfarm"
|
||||
description: "유튜브 다운로드"
|
||||
home: "https://gitea.yommi.duckdns.org/ff_plugins/youtube-dl"
|
||||
more: "https://gitea.yommi.duckdns.org/ff_plugins/youtube-dl/raw/branch/main/README.md"
|
||||
103
logic.py
103
logic.py
@@ -1,103 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import traceback
|
||||
import subprocess
|
||||
import sqlite3
|
||||
|
||||
from framework import db, path_app_root, path_data
|
||||
from framework.logger import get_logger
|
||||
from framework.util import Util
|
||||
|
||||
from .logic_normal import LogicNormal
|
||||
from .model import ModelSetting
|
||||
|
||||
package_name = __name__.split(".", maxsplit=1)[0]
|
||||
logger = get_logger(package_name)
|
||||
|
||||
|
||||
class Logic(object):
|
||||
db_default = {
|
||||
"db_version": "2",
|
||||
"youtube_dl_package": "0",
|
||||
"ffmpeg_path": ""
|
||||
if platform.system() != "Windows"
|
||||
else os.path.join(path_app_root, "bin", "Windows", "ffmpeg.exe"),
|
||||
"temp_path": os.path.join(path_data, "download_tmp"),
|
||||
"save_path": os.path.join(path_data, "download"),
|
||||
"default_filename": "",
|
||||
"proxy": "",
|
||||
}
|
||||
|
||||
@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()
|
||||
Logic.migration()
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s", e)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def plugin_load():
|
||||
try:
|
||||
logger.debug("%s plugin_load", package_name)
|
||||
Logic.db_init()
|
||||
|
||||
# youtube-dl 업데이트
|
||||
youtube_dl = LogicNormal.get_youtube_dl_package(
|
||||
ModelSetting.get("youtube_dl_package")
|
||||
)
|
||||
logger.debug(f"{youtube_dl} upgrade")
|
||||
logger.debug(
|
||||
subprocess.check_output(
|
||||
[sys.executable, "-m", "pip", "install", "--upgrade", youtube_dl],
|
||||
universal_newlines=True,
|
||||
)
|
||||
)
|
||||
|
||||
# 편의를 위해 json 파일 생성
|
||||
from .plugin import plugin_info
|
||||
|
||||
Util.save_from_dict_to_json(
|
||||
plugin_info, os.path.join(os.path.dirname(__file__), "info.json")
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s", e)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def plugin_unload():
|
||||
try:
|
||||
logger.debug("%s plugin_unload", package_name)
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s", e)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def migration():
|
||||
try:
|
||||
db_version = ModelSetting.get_int("db_version")
|
||||
connect = sqlite3.connect(
|
||||
os.path.join(path_data, "db", f"{package_name}.db")
|
||||
)
|
||||
|
||||
if db_version < 2:
|
||||
logger.debug("youtube-dlc uninstall")
|
||||
logger.debug(
|
||||
subprocess.check_output(
|
||||
[sys.executable, "-m", "pip", "uninstall", "-y", "youtube-dlc"],
|
||||
universal_newlines=True,
|
||||
)
|
||||
)
|
||||
|
||||
connect.commit()
|
||||
connect.close()
|
||||
ModelSetting.set("db_version", Logic.db_default["db_version"])
|
||||
db.session.flush()
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s", e)
|
||||
logger.error(traceback.format_exc())
|
||||
@@ -1,28 +1,226 @@
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import traceback
|
||||
import subprocess
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
|
||||
from flask import jsonify
|
||||
|
||||
from framework.logger import get_logger
|
||||
from flask import render_template, jsonify
|
||||
|
||||
from .my_youtube_dl import MyYoutubeDL, Status
|
||||
|
||||
package_name = __name__.split(".", maxsplit=1)[0]
|
||||
logger = get_logger(package_name)
|
||||
logger = P.logger
|
||||
package_name = P.package_name
|
||||
ModelSetting = P.ModelSetting
|
||||
|
||||
|
||||
class LogicNormal(object):
|
||||
class LogicMain(LogicModuleBase):
|
||||
db_default = {
|
||||
"db_version": "2",
|
||||
"youtube_dl_package": "1",
|
||||
"ffmpeg_path": ""
|
||||
if platform.system() != "Windows"
|
||||
else os.path.join(path_app_root, "bin", "Windows", "ffmpeg.exe"),
|
||||
"temp_path": os.path.join(path_data, "download_tmp"),
|
||||
"save_path": os.path.join(path_data, "download"),
|
||||
"default_filename": "",
|
||||
"proxy": "",
|
||||
}
|
||||
|
||||
def __init__(self, plugin):
|
||||
super(LogicMain, self).__init__(plugin, None)
|
||||
self.name = package_name # 모듈명
|
||||
default_route_socketio(plugin, self)
|
||||
|
||||
def plugin_load(self):
|
||||
try:
|
||||
# youtube-dl 업데이트
|
||||
youtube_dl = Plugin.youtube_dl_packages[
|
||||
int(ModelSetting.get("youtube_dl_package"))
|
||||
]
|
||||
logger.debug(f"{youtube_dl} upgrade")
|
||||
logger.debug(
|
||||
subprocess.check_output(
|
||||
[sys.executable, "-m", "pip", "install", "--upgrade", youtube_dl],
|
||||
universal_newlines=True,
|
||||
)
|
||||
)
|
||||
except Exception as error:
|
||||
logger.error("Exception:%s", error)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
def process_menu(self, sub, req):
|
||||
try:
|
||||
arg = {
|
||||
"package_name": package_name,
|
||||
"sub": sub,
|
||||
"template_name": f"{package_name}_{sub}",
|
||||
"package_version": Plugin.plugin_info["version"],
|
||||
}
|
||||
|
||||
if sub == "setting":
|
||||
arg.update(ModelSetting.to_dict())
|
||||
arg["package_list"] = Plugin.youtube_dl_packages
|
||||
arg["youtube_dl_version"] = LogicMain.get_youtube_dl_version()
|
||||
arg["DEFAULT_FILENAME"] = LogicMain.get_default_filename()
|
||||
|
||||
elif sub == "download":
|
||||
default_filename = ModelSetting.get("default_filename")
|
||||
arg["filename"] = (
|
||||
default_filename
|
||||
if default_filename
|
||||
else LogicMain.get_default_filename()
|
||||
)
|
||||
arg["preset_list"] = LogicMain.get_preset_list()
|
||||
arg["postprocessor_list"] = LogicMain.get_postprocessor_list()
|
||||
|
||||
elif sub == "thumbnail":
|
||||
default_filename = ModelSetting.get("default_filename")
|
||||
arg["filename"] = (
|
||||
default_filename
|
||||
if default_filename
|
||||
else LogicMain.get_default_filename()
|
||||
)
|
||||
|
||||
elif sub == "sub":
|
||||
default_filename = ModelSetting.get("default_filename")
|
||||
arg["filename"] = (
|
||||
default_filename
|
||||
if default_filename
|
||||
else LogicMain.get_default_filename()
|
||||
)
|
||||
|
||||
elif sub == "list":
|
||||
pass
|
||||
|
||||
return render_template(f"{package_name}_{sub}.html", arg=arg)
|
||||
except Exception as error:
|
||||
logger.error("Exception:%s", error)
|
||||
logger.error(traceback.format_exc())
|
||||
return render_template("sample.html", title=f"{package_name} - {sub}")
|
||||
|
||||
def process_ajax(self, sub, req):
|
||||
try:
|
||||
logger.debug("AJAX: %s, %s", sub, req.values)
|
||||
ret = {"ret": "success"}
|
||||
|
||||
if sub == "ffmpeg_version":
|
||||
path = req.form["path"]
|
||||
output = subprocess.check_output([path, "-version"])
|
||||
output = output.decode().replace("\n", "<br>")
|
||||
ret["data"] = output
|
||||
|
||||
elif sub == "download":
|
||||
postprocessor = req.form["postprocessor"]
|
||||
video_convertor, extract_audio = LogicMain.get_postprocessor()
|
||||
preferedformat = None
|
||||
preferredcodec = None
|
||||
preferredquality = None
|
||||
if postprocessor in video_convertor:
|
||||
preferedformat = postprocessor
|
||||
elif postprocessor in extract_audio:
|
||||
preferredcodec = postprocessor
|
||||
preferredquality = 192
|
||||
youtube_dl = LogicMain.download(
|
||||
plugin=package_name,
|
||||
url=req.form["url"],
|
||||
filename=req.form["filename"],
|
||||
temp_path=ModelSetting.get("temp_path"),
|
||||
save_path=ModelSetting.get("save_path"),
|
||||
format=req.form["format"],
|
||||
preferedformat=preferedformat,
|
||||
preferredcodec=preferredcodec,
|
||||
preferredquality=preferredquality,
|
||||
proxy=ModelSetting.get("proxy"),
|
||||
ffmpeg_path=ModelSetting.get("ffmpeg_path"),
|
||||
)
|
||||
youtube_dl.start()
|
||||
LogicMain.socketio_emit("add", youtube_dl)
|
||||
ret["ret"] = "info"
|
||||
ret["msg"] = "분석중..."
|
||||
|
||||
elif sub == "thumbnail":
|
||||
youtube_dl = LogicMain.thumbnail(
|
||||
plugin=package_name,
|
||||
url=req.form["url"],
|
||||
filename=req.form["filename"],
|
||||
temp_path=ModelSetting.get("temp_path"),
|
||||
save_path=ModelSetting.get("save_path"),
|
||||
all_thumbnails=req.form["all_thumbnails"],
|
||||
proxy=ModelSetting.get("proxy"),
|
||||
ffmpeg_path=ModelSetting.get("ffmpeg_path"),
|
||||
)
|
||||
youtube_dl.start()
|
||||
LogicMain.socketio_emit("add", youtube_dl)
|
||||
ret["ret"] = "info"
|
||||
ret["msg"] = "분석중..."
|
||||
|
||||
elif sub == "sub":
|
||||
youtube_dl = LogicMain.sub(
|
||||
plugin=package_name,
|
||||
url=req.form["url"],
|
||||
filename=req.form["filename"],
|
||||
temp_path=ModelSetting.get("temp_path"),
|
||||
save_path=ModelSetting.get("save_path"),
|
||||
all_subs=req.form["all_subs"],
|
||||
sub_lang=req.form["sub_lang"],
|
||||
auto_sub=req.form["auto_sub"],
|
||||
proxy=ModelSetting.get("proxy"),
|
||||
ffmpeg_path=ModelSetting.get("ffmpeg_path"),
|
||||
)
|
||||
youtube_dl.start()
|
||||
LogicMain.socketio_emit("add", youtube_dl)
|
||||
ret["ret"] = "info"
|
||||
ret["msg"] = "분석중..."
|
||||
|
||||
elif sub == "list":
|
||||
ret["data"] = []
|
||||
for i in LogicMain.youtube_dl_list:
|
||||
data = LogicMain.get_data(i)
|
||||
if data is not None:
|
||||
ret["data"].append(data)
|
||||
|
||||
elif sub == "all_stop":
|
||||
for i in LogicMain.youtube_dl_list:
|
||||
i.stop()
|
||||
|
||||
elif sub == "stop":
|
||||
index = int(req.form["index"])
|
||||
LogicMain.youtube_dl_list[index].stop()
|
||||
|
||||
return jsonify(ret)
|
||||
except Exception as error:
|
||||
logger.error("Exception:%s", error)
|
||||
logger.error(traceback.format_exc())
|
||||
return jsonify({"ret": "danger", "msg": str(error)})
|
||||
|
||||
def migration(self):
|
||||
try:
|
||||
db_version = ModelSetting.get_int("db_version")
|
||||
connect = sqlite3.connect(
|
||||
os.path.join(path_data, "db", f"{package_name}.db")
|
||||
)
|
||||
|
||||
if db_version < 2:
|
||||
logger.debug("youtube-dlc uninstall")
|
||||
logger.debug(
|
||||
subprocess.check_output(
|
||||
[sys.executable, "-m", "pip", "uninstall", "-y", "youtube-dlc"],
|
||||
universal_newlines=True,
|
||||
)
|
||||
)
|
||||
|
||||
connect.commit()
|
||||
connect.close()
|
||||
ModelSetting.set("db_version", LogicMain.db_default["db_version"])
|
||||
db.session.flush()
|
||||
except Exception as error:
|
||||
logger.error("Exception:%s", error)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
youtube_dl_list = []
|
||||
|
||||
@staticmethod
|
||||
def get_youtube_dl_package(index=None, import_pkg=False):
|
||||
packages = ["youtube-dl", "yt-dlp"]
|
||||
import_name = ["youtube_dl", "yt_dlp"]
|
||||
if import_pkg:
|
||||
return import_name if index is None else import_name[int(index)]
|
||||
else:
|
||||
return packages if index is None else packages[int(index)]
|
||||
|
||||
@staticmethod
|
||||
def get_youtube_dl_version():
|
||||
try:
|
||||
@@ -79,7 +277,7 @@ class LogicNormal(object):
|
||||
def get_postprocessor():
|
||||
video_convertor = []
|
||||
extract_audio = []
|
||||
for i in LogicNormal.get_postprocessor_list():
|
||||
for i in LogicMain.get_postprocessor_list():
|
||||
if i[2] == "비디오 변환":
|
||||
video_convertor.append(i[0])
|
||||
elif i[2] == "오디오 추출":
|
||||
@@ -131,12 +329,14 @@ class LogicNormal(object):
|
||||
opts["ffmpeg_location"] = kwagrs["ffmpeg_path"]
|
||||
if "cookiefile" in kwagrs and kwagrs["cookiefile"]:
|
||||
opts["cookiefile"] = kwagrs["cookiefile"]
|
||||
if "headers" in kwagrs and kwagrs["headers"]:
|
||||
opts["http_headers"] = kwagrs["headers"]
|
||||
dateafter = kwagrs.get("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) # 리스트 추가
|
||||
LogicMain.youtube_dl_list.append(youtube_dl) # 리스트 추가
|
||||
return youtube_dl
|
||||
except Exception as error:
|
||||
logger.error("Exception:%s", error)
|
||||
@@ -175,6 +375,8 @@ class LogicNormal(object):
|
||||
opts["ffmpeg_location"] = kwagrs["ffmpeg_path"]
|
||||
if "cookiefile" in kwagrs and kwagrs["cookiefile"]:
|
||||
opts["cookiefile"] = kwagrs["cookiefile"]
|
||||
if "headers" in kwagrs and kwagrs["headers"]:
|
||||
opts["http_headers"] = kwagrs["headers"]
|
||||
dateafter = kwagrs.get("dateafter")
|
||||
youtube_dl = MyYoutubeDL(
|
||||
plugin,
|
||||
@@ -187,7 +389,7 @@ class LogicNormal(object):
|
||||
dateafter,
|
||||
)
|
||||
youtube_dl.key = kwagrs.get("key")
|
||||
LogicNormal.youtube_dl_list.append(youtube_dl) # 리스트 추가
|
||||
LogicMain.youtube_dl_list.append(youtube_dl) # 리스트 추가
|
||||
return youtube_dl
|
||||
except Exception as error:
|
||||
logger.error("Exception:%s", error)
|
||||
@@ -230,12 +432,14 @@ class LogicNormal(object):
|
||||
opts["ffmpeg_location"] = kwagrs["ffmpeg_path"]
|
||||
if "cookiefile" in kwagrs and kwagrs["cookiefile"]:
|
||||
opts["cookiefile"] = kwagrs["cookiefile"]
|
||||
if "headers" in kwagrs and kwagrs["headers"]:
|
||||
opts["http_headers"] = kwagrs["headers"]
|
||||
dateafter = kwagrs.get("dateafter")
|
||||
youtube_dl = MyYoutubeDL(
|
||||
plugin, "subtitle", url, filename, temp_path, save_path, opts, dateafter
|
||||
)
|
||||
youtube_dl.key = kwagrs.get("key")
|
||||
LogicNormal.youtube_dl_list.append(youtube_dl) # 리스트 추가
|
||||
LogicMain.youtube_dl_list.append(youtube_dl) # 리스트 추가
|
||||
return youtube_dl
|
||||
except Exception as error:
|
||||
logger.error("Exception:%s", error)
|
||||
@@ -284,9 +488,7 @@ class LogicNormal(object):
|
||||
else ""
|
||||
)
|
||||
data["speed_str"] = (
|
||||
LogicNormal.human_readable_size(
|
||||
youtube_dl.progress_hooks["speed"], "/s"
|
||||
)
|
||||
LogicMain.human_readable_size(youtube_dl.progress_hooks["speed"], "/s")
|
||||
if youtube_dl.progress_hooks["speed"] is not None
|
||||
else ""
|
||||
)
|
||||
@@ -303,19 +505,19 @@ class LogicNormal(object):
|
||||
youtube_dl.progress_hooks["downloaded_bytes"],
|
||||
youtube_dl.progress_hooks["total_bytes"],
|
||||
): # 둘 다 값이 있으면
|
||||
data["downloaded_bytes_str"] = LogicNormal.human_readable_size(
|
||||
data["downloaded_bytes_str"] = LogicMain.human_readable_size(
|
||||
youtube_dl.progress_hooks["downloaded_bytes"]
|
||||
)
|
||||
data["total_bytes_str"] = LogicNormal.human_readable_size(
|
||||
data["total_bytes_str"] = LogicMain.human_readable_size(
|
||||
youtube_dl.progress_hooks["total_bytes"]
|
||||
)
|
||||
data[
|
||||
"percent"
|
||||
] = f"{float(youtube_dl.progress_hooks['downloaded_bytes']) / float(youtube_dl.progress_hooks['total_bytes']) * 100:.2f}"
|
||||
] = f"{(float(youtube_dl.progress_hooks['downloaded_bytes']) / float(youtube_dl.progress_hooks['total_bytes']) * 100):.2f}"
|
||||
data["start_time"] = youtube_dl.start_time.strftime("%m-%d %H:%M:%S")
|
||||
data[
|
||||
"download_time"
|
||||
] = f"{download_time.seconds / 60:02f}:{download_time.seconds % 60:02f}"
|
||||
] = f"{int(download_time.seconds / 60):02d}:{int(download_time.seconds % 60):02d}"
|
||||
return data
|
||||
except Exception as error:
|
||||
logger.error("Exception:%s", error)
|
||||
@@ -335,6 +537,7 @@ class LogicNormal(object):
|
||||
return f"{size:.1f} YB{suffix}"
|
||||
|
||||
@staticmethod
|
||||
def abort(base, code):
|
||||
base["errorCode"] = code
|
||||
return jsonify(base)
|
||||
def socketio_emit(cmd, data):
|
||||
socketio.emit(
|
||||
cmd, LogicMain.get_data(data), namespace=f"/{package_name}", broadcast=True
|
||||
)
|
||||
118
mod_basic.py
Normal file
118
mod_basic.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/02/25 7:29 PM
|
||||
# @Author : yommi
|
||||
# @Site :
|
||||
# @File : mod_basic
|
||||
# @Software: PyCharm
|
||||
# @Path : youtube-dl/mod_basic.py
|
||||
from support import SupportYaml
|
||||
from tool import ToolUtil
|
||||
|
||||
from .setup import *
|
||||
|
||||
# from .main import LogicMain
|
||||
from .my_youtube_dl import MyYoutubeDL, Status
|
||||
import platform
|
||||
import os
|
||||
from .model import ModelYoutubeDlItem
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class ModuleBasic(PluginModuleBase):
|
||||
def __init__(self, P):
|
||||
super(ModuleBasic, self).__init__(
|
||||
P, name="basic", first_menu="setting", scheduler_desc="유튜브 다운로더"
|
||||
)
|
||||
self.db_default = {
|
||||
"db_version": "2",
|
||||
"youtube_dl_package": "1",
|
||||
"ffmpeg_path": ""
|
||||
if platform.system() != "Windows"
|
||||
else os.path.join("PATH_APP_ROOT", "bin", "Windows", "ffmpeg.exe"),
|
||||
"temp_path": os.path.join("{PATH_DATA}", "download_tmp"),
|
||||
"save_path": os.path.join("{PATH_DATA}", "download"),
|
||||
"default_filename": "",
|
||||
"proxy": "",
|
||||
}
|
||||
self.web_list_model = ModelYoutubeDlItem
|
||||
|
||||
def process_menu(self, sub, req):
|
||||
logger.debug(f"sub: {sub}")
|
||||
arg = P.ModelSetting.to_dict()
|
||||
logger.debug(f"arg:: {arg}")
|
||||
if sub == "setting":
|
||||
arg["is_include"] = F.scheduler.is_include(self.get_scheduler_name())
|
||||
arg["is_running"] = F.scheduler.is_running(self.get_scheduler_name())
|
||||
|
||||
elif sub == "download":
|
||||
default_filename = P.ModelSetting.get("default_filename")
|
||||
arg["filename"] = (
|
||||
default_filename
|
||||
if default_filename
|
||||
else ModuleBasic.get_default_filename()
|
||||
)
|
||||
arg["preset_list"] = ModuleBasic.get_preset_list()
|
||||
arg["postprocessor_list"] = ModuleBasic.get_postprocessor_list()
|
||||
|
||||
return render_template(f"{P.package_name}_{self.name}_{sub}.html", arg=arg)
|
||||
|
||||
def process_command(self, command, arg1, arg2, arg3, req):
|
||||
ret = {"ret": "success"}
|
||||
return jsonify(ret)
|
||||
|
||||
# def plugin_load(self):
|
||||
# if (
|
||||
# os.path.exists(
|
||||
# ToolUtil.make_path(P.ModelSetting.get(f"{self.name}_path_config"))
|
||||
# )
|
||||
# is False
|
||||
# ):
|
||||
# shutil.copyfile(
|
||||
# os.path.join(
|
||||
# os.path.dirname(__file__), "files", f"config_{self.name}.yaml"
|
||||
# ),
|
||||
# ToolUtil.make_path(P.ModelSetting.get(f"{self.name}_path_config")),
|
||||
# )
|
||||
|
||||
@staticmethod
|
||||
def get_default_filename():
|
||||
return MyYoutubeDL.DEFAULT_FILENAME
|
||||
|
||||
@staticmethod
|
||||
def get_preset_list():
|
||||
return [
|
||||
["bestvideo+bestaudio/best", "최고 화질"],
|
||||
["bestvideo[height<=1080]+bestaudio/best[height<=1080]", "1080p"],
|
||||
["worstvideo+worstaudio/worst", "최저 화질"],
|
||||
["bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]", "최고 화질(mp4)"],
|
||||
[
|
||||
"bestvideo[ext=mp4][height<=1080]+bestaudio[ext=m4a]/best[ext=mp4][height<=1080]",
|
||||
"1080p(mp4)",
|
||||
],
|
||||
["bestvideo[filesize<50M]+bestaudio/best[filesize<50M]", "50MB 미만"],
|
||||
["bestaudio/best", "오디오만"],
|
||||
["_custom", "사용자 정의"],
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_postprocessor_list():
|
||||
return [
|
||||
["", "후처리 안함", None],
|
||||
["mp4", "MP4", "비디오 변환"],
|
||||
["flv", "FLV", "비디오 변환"],
|
||||
["webm", "WebM", "비디오 변환"],
|
||||
["ogg", "Ogg", "비디오 변환"],
|
||||
["mkv", "MKV", "비디오 변환"],
|
||||
["ts", "TS", "비디오 변환"],
|
||||
["avi", "AVI", "비디오 변환"],
|
||||
["wmv", "WMV", "비디오 변환"],
|
||||
["mov", "MOV", "비디오 변환"],
|
||||
["gif", "GIF", "비디오 변환"],
|
||||
["mp3", "MP3", "오디오 추출"],
|
||||
["aac", "AAC", "오디오 추출"],
|
||||
["flac", "FLAC", "오디오 추출"],
|
||||
["m4a", "M4A", "오디오 추출"],
|
||||
["opus", "Opus", "오디오 추출"],
|
||||
["vorbis", "Vorbis", "오디오 추출"],
|
||||
["wav", "WAV", "오디오 추출"],
|
||||
]
|
||||
125
model.py
125
model.py
@@ -1,119 +1,18 @@
|
||||
import os
|
||||
import traceback
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/02/25 7:55 PM
|
||||
# @Author : yommi
|
||||
# @Site :
|
||||
# @File : model
|
||||
# @Software: PyCharm
|
||||
# @Path : youtube-dl/model.py
|
||||
|
||||
from framework import app, db, path_data
|
||||
from framework.logger import get_logger
|
||||
from framework.util import Util
|
||||
|
||||
package_name = __name__.split(".", maxsplit=1)[0]
|
||||
logger = get_logger(package_name)
|
||||
app.config["SQLALCHEMY_BINDS"][package_name] = "sqlite:///%s" % (
|
||||
os.path.join(path_data, "db", f"{package_name}.db")
|
||||
)
|
||||
from .setup import *
|
||||
|
||||
|
||||
class ModelSetting(db.Model):
|
||||
__tablename__ = f"{package_name}_setting"
|
||||
class ModelYoutubeDlItem(ModelBase):
|
||||
P = P
|
||||
__tablename__ = "youtube_dl_item"
|
||||
__table_args__ = {"mysql_collate": "utf8_general_ci"}
|
||||
__bind_key__ = package_name
|
||||
__bind_key__ = P.package_name
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
key = db.Column(db.String(100), unique=True, nullable=False)
|
||||
value = db.Column(db.String, nullable=False)
|
||||
|
||||
def __init__(self, key, value):
|
||||
self.key = key
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.as_dict())
|
||||
|
||||
def as_dict(self):
|
||||
return {x.name: getattr(self, x.name) for x in self.__table__.columns}
|
||||
|
||||
@staticmethod
|
||||
def get(key):
|
||||
try:
|
||||
return (
|
||||
db.session.query(ModelSetting).filter_by(key=key).first().value.strip()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s %s", e, key)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def get_int(key):
|
||||
try:
|
||||
return int(ModelSetting.get(key))
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s %s", e, key)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def get_bool(key):
|
||||
try:
|
||||
return ModelSetting.get(key) == "True"
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s %s", e, key)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def set(key, value):
|
||||
try:
|
||||
item = (
|
||||
db.session.query(ModelSetting)
|
||||
.filter_by(key=key)
|
||||
.with_for_update()
|
||||
.first()
|
||||
)
|
||||
if item is not None:
|
||||
item.value = value.strip()
|
||||
db.session.commit()
|
||||
else:
|
||||
db.session.add(ModelSetting(key, value.strip()))
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s", e)
|
||||
logger.error(traceback.format_exc())
|
||||
logger.error("Error Key:%s Value:%s", key, value)
|
||||
|
||||
@staticmethod
|
||||
def to_dict():
|
||||
try:
|
||||
return Util.db_list_to_dict(db.session.query(ModelSetting).all())
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s", e)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@staticmethod
|
||||
def setting_save(req):
|
||||
try:
|
||||
for key, value in req.form.items():
|
||||
if key in ["scheduler", "is_running"]:
|
||||
continue
|
||||
if key.startswith("tmp_"):
|
||||
continue
|
||||
logger.debug("Key:%s Value:%s", key, value)
|
||||
entity = (
|
||||
db.session.query(ModelSetting)
|
||||
.filter_by(key=key)
|
||||
.with_for_update()
|
||||
.first()
|
||||
)
|
||||
entity.value = value
|
||||
db.session.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s", e)
|
||||
logger.error(traceback.format_exc())
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_list(key):
|
||||
try:
|
||||
value = ModelSetting.get(key)
|
||||
values = [x.strip().strip() for x in value.replace("\n", "|").split("|")]
|
||||
values = Util.get_list_except_empty(values)
|
||||
return values
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s %s", e, key)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@@ -8,11 +8,20 @@ from datetime import datetime
|
||||
from threading import Thread
|
||||
from enum import Enum
|
||||
|
||||
from framework.logger import get_logger
|
||||
import framework.common.celery as celery_shutil
|
||||
from framework import celery as celery_shutil
|
||||
|
||||
package_name = __name__.split(".", maxsplit=1)[0]
|
||||
logger = get_logger(package_name)
|
||||
# from .plugin import Plugin
|
||||
|
||||
from .setup import P
|
||||
|
||||
logger = P.logger
|
||||
ModelSetting = P.ModelSetting
|
||||
|
||||
youtube_dl_package = P.youtube_dl_packages[
|
||||
int(ModelSetting.get("youtube_dl_package"))
|
||||
if ModelSetting.get("youtube_dl_package")
|
||||
else 1 # LogicMain.db_default["youtube_dl_package"]
|
||||
].replace("-", "_")
|
||||
|
||||
|
||||
class Status(Enum):
|
||||
@@ -25,11 +34,11 @@ class Status(Enum):
|
||||
COMPLETED = 6
|
||||
|
||||
def __str__(self):
|
||||
str_list = ["??", "???", "?????", "??", "???", "??", "??"]
|
||||
str_list = ["준비", "분석중", "다운로드중", "실패", "변환중", "중지", "완료"]
|
||||
return str_list[self.value]
|
||||
|
||||
|
||||
class MyYoutubeDL(object):
|
||||
class MyYoutubeDL:
|
||||
DEFAULT_FILENAME = "%(title)s-%(id)s.%(ext)s"
|
||||
|
||||
_index = 0
|
||||
@@ -47,8 +56,6 @@ class MyYoutubeDL(object):
|
||||
datebefore=None,
|
||||
):
|
||||
# from youtube_dl.utils import DateRange
|
||||
from .plugin import youtube_dl_package
|
||||
|
||||
DateRange = __import__(
|
||||
f"{youtube_dl_package}.utils", fromlist=["DateRange"]
|
||||
).DateRange
|
||||
@@ -79,10 +86,10 @@ class MyYoutubeDL(object):
|
||||
self.end_time = None # 종료 시간
|
||||
# info_dict에서 얻는 정보
|
||||
self.info_dict = {
|
||||
"extractor": None, # ??
|
||||
"title": None, # ??
|
||||
"uploader": None, # ???
|
||||
"uploader_url": None, # ??? ??
|
||||
"extractor": None, # 타입
|
||||
"title": None, # 제목
|
||||
"uploader": None, # 업로더
|
||||
"uploader_url": None, # 업로더 주소
|
||||
}
|
||||
# info_dict에서 얻는 정보(entries)
|
||||
# self.info_dict['playlist_index'] = None
|
||||
@@ -91,10 +98,10 @@ class MyYoutubeDL(object):
|
||||
# self.info_dict['thumbnail'] = None # 썸네일
|
||||
# progress_hooks에서 얻는 정보
|
||||
self.progress_hooks = {
|
||||
"downloaded_bytes": None, # ????? ??
|
||||
"total_bytes": None, # ?? ??
|
||||
"eta": None, # ?? ??(s)
|
||||
"speed": None, # ???? ??(bytes/s)
|
||||
"downloaded_bytes": None, # 다운로드한 크기
|
||||
"total_bytes": None, # 전체 크기
|
||||
"eta": None, # 예상 시간(s)
|
||||
"speed": None, # 다운로드 속도(bytes/s)
|
||||
}
|
||||
|
||||
def start(self):
|
||||
@@ -106,8 +113,6 @@ class MyYoutubeDL(object):
|
||||
|
||||
def run(self):
|
||||
# import youtube_dl
|
||||
from .plugin import youtube_dl_package
|
||||
|
||||
youtube_dl = __import__(youtube_dl_package)
|
||||
|
||||
try:
|
||||
@@ -115,7 +120,10 @@ class MyYoutubeDL(object):
|
||||
self.status = Status.START
|
||||
# 동영상 정보 가져오기
|
||||
info_dict = MyYoutubeDL.get_info_dict(
|
||||
self.url, self.opts.get("proxy"), self.opts.get("cookiefile")
|
||||
self.url,
|
||||
self.opts.get("proxy"),
|
||||
self.opts.get("cookiefile"),
|
||||
self.opts.get("http_headers"),
|
||||
)
|
||||
if info_dict is None:
|
||||
self.status = Status.ERROR
|
||||
@@ -134,7 +142,9 @@ class MyYoutubeDL(object):
|
||||
}
|
||||
ydl_opts.update(self.opts)
|
||||
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
||||
ydl.download([self.url])
|
||||
logger.debug(self.url)
|
||||
error_code = ydl.download([self.url])
|
||||
logger.debug(error_code)
|
||||
if self.status in (Status.START, Status.FINISHED): # 다운로드 성공
|
||||
for i in glob(self.temp_path + "/**/*", recursive=True):
|
||||
path = i.replace(self.temp_path, self.save_path, 1)
|
||||
@@ -144,9 +154,9 @@ class MyYoutubeDL(object):
|
||||
continue
|
||||
celery_shutil.move(i, path)
|
||||
self.status = Status.COMPLETED
|
||||
except Exception as e:
|
||||
except Exception as error:
|
||||
self.status = Status.ERROR
|
||||
logger.error("Exception:%s", e)
|
||||
logger.error("Exception:%s", error)
|
||||
logger.error(traceback.format_exc())
|
||||
finally:
|
||||
# 임시폴더 삭제
|
||||
@@ -164,8 +174,6 @@ class MyYoutubeDL(object):
|
||||
@staticmethod
|
||||
def get_version():
|
||||
# from youtube_dl.version import __version__
|
||||
from .plugin import youtube_dl_package
|
||||
|
||||
__version__ = __import__(
|
||||
f"{youtube_dl_package}.version", fromlist=["__version__"]
|
||||
).__version__
|
||||
@@ -173,10 +181,8 @@ class MyYoutubeDL(object):
|
||||
return __version__
|
||||
|
||||
@staticmethod
|
||||
def get_info_dict(url, proxy=None, cookiefile=None):
|
||||
def get_info_dict(url, proxy=None, cookiefile=None, http_headers=None):
|
||||
# import youtube_dl
|
||||
from .plugin import youtube_dl_package
|
||||
|
||||
youtube_dl = __import__(youtube_dl_package)
|
||||
|
||||
try:
|
||||
@@ -185,27 +191,30 @@ class MyYoutubeDL(object):
|
||||
ydl_opts["proxy"] = proxy
|
||||
if cookiefile:
|
||||
ydl_opts["cookiefile"] = cookiefile
|
||||
if http_headers:
|
||||
ydl_opts["http_headers"] = http_headers
|
||||
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
||||
# info = ydl.extract_info(url, download=False)
|
||||
info = ydl.extract_info(url, download=False)
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s", e)
|
||||
except Exception as error:
|
||||
logger.error("Exception:%s", error)
|
||||
logger.error(traceback.format_exc())
|
||||
return None
|
||||
return info
|
||||
return ydl.sanitize_info(info)
|
||||
|
||||
def my_hook(self, d):
|
||||
def my_hook(self, data):
|
||||
if self.status != Status.STOP:
|
||||
self.status = {
|
||||
"downloading": Status.DOWNLOADING,
|
||||
"error": Status.ERROR,
|
||||
"finished": Status.FINISHED, # ???? ??. ?? ??
|
||||
}[d["status"]]
|
||||
if d["status"] != "error":
|
||||
self.filename = os.path.basename(d.get("filename"))
|
||||
self.progress_hooks["downloaded_bytes"] = d.get("downloaded_bytes")
|
||||
self.progress_hooks["total_bytes"] = d.get("total_bytes")
|
||||
self.progress_hooks["eta"] = d.get("eta")
|
||||
self.progress_hooks["speed"] = d.get("speed")
|
||||
"finished": Status.FINISHED, # 다운로드 완료. 변환 시작
|
||||
}[data["status"]]
|
||||
if data["status"] != "error":
|
||||
self.filename = os.path.basename(data.get("filename"))
|
||||
self.progress_hooks["downloaded_bytes"] = data.get("downloaded_bytes")
|
||||
self.progress_hooks["total_bytes"] = data.get("total_bytes")
|
||||
self.progress_hooks["eta"] = data.get("eta")
|
||||
self.progress_hooks["speed"] = data.get("speed")
|
||||
|
||||
def match_filter_func(self, info_dict):
|
||||
self.info_dict["playlist_index"] = info_dict["playlist_index"]
|
||||
@@ -220,15 +229,15 @@ class MyYoutubeDL(object):
|
||||
|
||||
@status.setter
|
||||
def status(self, value):
|
||||
from .plugin import socketio_emit
|
||||
from .main import LogicMain
|
||||
|
||||
self._status = value
|
||||
socketio_emit("status", self)
|
||||
LogicMain.socketio_emit("status", self)
|
||||
|
||||
|
||||
class MyLogger(object):
|
||||
class MyLogger:
|
||||
def debug(self, msg):
|
||||
if msg.find("\x1B") != -1 or msg.find("{") != -1:
|
||||
if msg.find(" ETA ") != -1:
|
||||
# 과도한 로그 방지
|
||||
return
|
||||
logger.debug(msg)
|
||||
|
||||
502
plugin.py
502
plugin.py
@@ -1,502 +0,0 @@
|
||||
import os
|
||||
import traceback
|
||||
import subprocess
|
||||
|
||||
from flask import Blueprint, request, render_template, redirect, jsonify, abort
|
||||
from flask_login import login_required
|
||||
from flask_cors import cross_origin
|
||||
|
||||
from framework import check_api, socketio
|
||||
from framework.logger import get_logger
|
||||
|
||||
from .logic import Logic
|
||||
from .logic_normal import LogicNormal
|
||||
from .model import ModelSetting
|
||||
|
||||
package_name = __name__.split(".", maxsplit=1)[0]
|
||||
logger = get_logger(package_name)
|
||||
youtube_dl_package = LogicNormal.get_youtube_dl_package(
|
||||
ModelSetting.get("youtube_dl_package")
|
||||
if ModelSetting.get("youtube_dl_package")
|
||||
else Logic.db_default["youtube_dl_package"],
|
||||
import_pkg=True,
|
||||
)
|
||||
|
||||
#########################################################
|
||||
# 플러그인 공용
|
||||
#########################################################
|
||||
blueprint = Blueprint(
|
||||
package_name,
|
||||
package_name,
|
||||
url_prefix=f"/{package_name}",
|
||||
template_folder=os.path.join(os.path.dirname(__file__), "templates"),
|
||||
static_folder=os.path.join(os.path.dirname(__file__), "static"),
|
||||
)
|
||||
|
||||
menu = {
|
||||
"main": [package_name, "youtube-dl"],
|
||||
"sub": [
|
||||
["setting", "설정"],
|
||||
["download", "다운로드"],
|
||||
["thumbnail", "썸네일 다운로드"],
|
||||
["sub", "자막 다운로드"],
|
||||
["list", "목록"],
|
||||
["log", "로그"],
|
||||
],
|
||||
"category": "vod",
|
||||
}
|
||||
|
||||
plugin_info = {
|
||||
"version": "3.1.0",
|
||||
"name": "youtube-dl",
|
||||
"category_name": "vod",
|
||||
"developer": "joyfuI",
|
||||
"description": "유튜브, 네이버TV 등 동영상 사이트에서 동영상 다운로드",
|
||||
"home": "https://github.com/joyfuI/youtube-dl",
|
||||
"more": "",
|
||||
}
|
||||
|
||||
|
||||
def plugin_load():
|
||||
Logic.plugin_load()
|
||||
|
||||
|
||||
def plugin_unload():
|
||||
Logic.plugin_unload()
|
||||
|
||||
|
||||
#########################################################
|
||||
# WEB Menu
|
||||
#########################################################
|
||||
@blueprint.route("/")
|
||||
def home():
|
||||
return redirect(f"/{package_name}/list")
|
||||
|
||||
|
||||
@blueprint.route("/<sub>")
|
||||
@login_required
|
||||
def first_menu(sub):
|
||||
try:
|
||||
arg = {
|
||||
"package_name": package_name,
|
||||
"template_name": f"{package_name}_{sub}",
|
||||
}
|
||||
|
||||
if sub == "setting":
|
||||
arg.update(ModelSetting.to_dict())
|
||||
arg["package_list"] = LogicNormal.get_youtube_dl_package()
|
||||
arg["youtube_dl_version"] = LogicNormal.get_youtube_dl_version()
|
||||
arg["DEFAULT_FILENAME"] = LogicNormal.get_default_filename()
|
||||
return render_template(f"{package_name}_{sub}.html", arg=arg)
|
||||
|
||||
elif sub == "download":
|
||||
default_filename = ModelSetting.get("default_filename")
|
||||
arg["filename"] = (
|
||||
default_filename
|
||||
if default_filename
|
||||
else LogicNormal.get_default_filename()
|
||||
)
|
||||
arg["preset_list"] = LogicNormal.get_preset_list()
|
||||
arg["postprocessor_list"] = LogicNormal.get_postprocessor_list()
|
||||
return render_template(f"{package_name}_{sub}.html", 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(f"{package_name}_{sub}.html", arg=arg)
|
||||
|
||||
elif sub == "sub":
|
||||
default_filename = ModelSetting.get("default_filename")
|
||||
arg["filename"] = (
|
||||
default_filename
|
||||
if default_filename
|
||||
else LogicNormal.get_default_filename()
|
||||
)
|
||||
return render_template(f"{package_name}_{sub}.html", arg=arg)
|
||||
|
||||
elif sub == "list":
|
||||
return render_template(f"{package_name}_{sub}.html", arg=arg)
|
||||
|
||||
elif sub == "log":
|
||||
return render_template("log.html", package=package_name)
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s", e)
|
||||
logger.error(traceback.format_exc())
|
||||
return render_template("sample.html", title=f"{package_name} - {sub}")
|
||||
|
||||
|
||||
#########################################################
|
||||
# For UI
|
||||
#########################################################
|
||||
@blueprint.route("/ajax/<sub>", methods=["POST"])
|
||||
@login_required
|
||||
def ajax(sub):
|
||||
logger.debug("AJAX %s %s", package_name, sub)
|
||||
try:
|
||||
# 공통 요청
|
||||
if sub == "setting_save":
|
||||
ret = ModelSetting.setting_save(request)
|
||||
if request.form["ffmpeg_path"] == "ffmpeg":
|
||||
ModelSetting.set("ffmpeg_path", "")
|
||||
return jsonify(ret)
|
||||
|
||||
# UI 요청
|
||||
elif sub == "ffmpeg_version":
|
||||
path = request.form["path"]
|
||||
ret = subprocess.check_output([path, "-version"])
|
||||
ret = ret.decode().replace("\n", "<br>")
|
||||
return jsonify(ret)
|
||||
|
||||
elif sub == "download":
|
||||
postprocessor = request.form["postprocessor"]
|
||||
video_convertor, extract_audio = LogicNormal.get_postprocessor()
|
||||
preferedformat = None
|
||||
preferredcodec = None
|
||||
preferredquality = None
|
||||
if postprocessor in video_convertor:
|
||||
preferedformat = postprocessor
|
||||
elif postprocessor in extract_audio:
|
||||
preferredcodec = postprocessor
|
||||
preferredquality = 192
|
||||
youtube_dl = LogicNormal.download(
|
||||
plugin=package_name,
|
||||
url=request.form["url"],
|
||||
filename=request.form["filename"],
|
||||
temp_path=ModelSetting.get("temp_path"),
|
||||
save_path=ModelSetting.get("save_path"),
|
||||
format=request.form["format"],
|
||||
preferedformat=preferedformat,
|
||||
preferredcodec=preferredcodec,
|
||||
preferredquality=preferredquality,
|
||||
proxy=ModelSetting.get("proxy"),
|
||||
ffmpeg_path=ModelSetting.get("ffmpeg_path"),
|
||||
)
|
||||
youtube_dl.start()
|
||||
socketio_emit("add", youtube_dl)
|
||||
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 == "sub":
|
||||
youtube_dl = LogicNormal.sub(
|
||||
plugin=package_name,
|
||||
url=request.form["url"],
|
||||
filename=request.form["filename"],
|
||||
temp_path=ModelSetting.get("temp_path"),
|
||||
save_path=ModelSetting.get("save_path"),
|
||||
all_subs=request.form["all_subs"],
|
||||
sub_lang=request.form["sub_lang"],
|
||||
auto_sub=request.form["auto_sub"],
|
||||
proxy=ModelSetting.get("proxy"),
|
||||
ffmpeg_path=ModelSetting.get("ffmpeg_path"),
|
||||
)
|
||||
youtube_dl.start()
|
||||
socketio_emit("add", youtube_dl)
|
||||
return jsonify([])
|
||||
|
||||
elif sub == "list":
|
||||
ret = []
|
||||
for i in LogicNormal.youtube_dl_list:
|
||||
data = LogicNormal.get_data(i)
|
||||
if data is not None:
|
||||
ret.append(data)
|
||||
return jsonify(ret)
|
||||
|
||||
elif sub == "all_stop":
|
||||
for i in LogicNormal.youtube_dl_list:
|
||||
i.stop()
|
||||
return jsonify([])
|
||||
|
||||
elif sub == "stop":
|
||||
index = int(request.form["index"])
|
||||
LogicNormal.youtube_dl_list[index].stop()
|
||||
return jsonify([])
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s", e)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
#########################################################
|
||||
# API
|
||||
#########################################################
|
||||
# API 명세는 https://github.com/joyfuI/youtube-dl#api
|
||||
@blueprint.route("/api/<sub>", methods=["GET", "POST"])
|
||||
@cross_origin()
|
||||
@check_api
|
||||
def api(sub):
|
||||
plugin = request.values.get("plugin")
|
||||
logger.debug("API %s %s: %s", package_name, sub, plugin)
|
||||
if not plugin: # 요청한 플러그인명이 빈문자열이거나 None면
|
||||
abort(403) # 403 에러(거부)
|
||||
try:
|
||||
# 동영상 정보를 반환하는 API
|
||||
if sub == "info_dict":
|
||||
url = request.values.get("url")
|
||||
ret = {"errorCode": 0, "info_dict": None}
|
||||
if None in (url,):
|
||||
return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
|
||||
if not url.startswith("http"):
|
||||
return LogicNormal.abort(ret, 2) # 잘못된 동영상 주소
|
||||
info_dict = LogicNormal.get_info_dict(url, ModelSetting.get("proxy"))
|
||||
if info_dict is None:
|
||||
return LogicNormal.abort(ret, 10) # 실패
|
||||
ret["info_dict"] = info_dict
|
||||
return jsonify(ret)
|
||||
|
||||
# 비디오 다운로드 준비를 요청하는 API
|
||||
elif sub == "download":
|
||||
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"))
|
||||
format_code = request.values.get("format", None)
|
||||
preferedformat = request.values.get("preferedformat", None)
|
||||
preferredcodec = request.values.get("preferredcodec", None)
|
||||
preferredquality = request.values.get("preferredquality", 192)
|
||||
dateafter = request.values.get("dateafter", None)
|
||||
playlist = request.values.get("playlist", 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 preferredcodec not in (
|
||||
None,
|
||||
"best",
|
||||
"mp3",
|
||||
"aac",
|
||||
"flac",
|
||||
"m4a",
|
||||
"opus",
|
||||
"vorbis",
|
||||
"wav",
|
||||
):
|
||||
return LogicNormal.abort(ret, 5) # 허용되지 않은 값이 있음
|
||||
if not filename:
|
||||
filename = LogicNormal.get_default_filename()
|
||||
youtube_dl = LogicNormal.download(
|
||||
plugin=plugin,
|
||||
url=url,
|
||||
filename=filename,
|
||||
temp_path=ModelSetting.get("temp_path"),
|
||||
save_path=save_path,
|
||||
format=format_code,
|
||||
preferedformat=preferedformat,
|
||||
preferredcodec=preferredcodec,
|
||||
preferredquality=preferredquality,
|
||||
dateafter=dateafter,
|
||||
playlist=playlist,
|
||||
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
|
||||
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)
|
||||
playlist = request.values.get("playlist", 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,
|
||||
playlist=playlist,
|
||||
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
|
||||
elif sub == "sub":
|
||||
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_subs = request.values.get("all_subs", False)
|
||||
sub_lang = request.values.get("sub_lang", "ko")
|
||||
auto_sub = request.values.get("all_subs", False)
|
||||
dateafter = request.values.get("dateafter", None)
|
||||
playlist = request.values.get("playlist", 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.sub(
|
||||
plugin=plugin,
|
||||
url=url,
|
||||
filename=filename,
|
||||
temp_path=ModelSetting.get("temp_path"),
|
||||
save_path=save_path,
|
||||
all_subs=all_subs,
|
||||
sub_lang=sub_lang,
|
||||
auto_sub=auto_sub,
|
||||
dateafter=dateafter,
|
||||
playlist=playlist,
|
||||
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
|
||||
elif sub == "start":
|
||||
index = request.values.get("index")
|
||||
key = request.values.get("key")
|
||||
ret = {"errorCode": 0, "status": None}
|
||||
if None in (index, key):
|
||||
return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
|
||||
index = int(index)
|
||||
if not 0 <= index < len(LogicNormal.youtube_dl_list):
|
||||
return LogicNormal.abort(ret, 3) # 인덱스 범위를 벗어남
|
||||
youtube_dl = LogicNormal.youtube_dl_list[index]
|
||||
if youtube_dl.key != key:
|
||||
return LogicNormal.abort(ret, 4) # 키가 일치하지 않음
|
||||
ret["status"] = youtube_dl.status.name
|
||||
if not youtube_dl.start():
|
||||
return LogicNormal.abort(ret, 10) # 실패
|
||||
return jsonify(ret)
|
||||
|
||||
# 다운로드 중지를 요청하는 API
|
||||
elif sub == "stop":
|
||||
index = request.values.get("index")
|
||||
key = request.values.get("key")
|
||||
ret = {"errorCode": 0, "status": None}
|
||||
if None in (index, key):
|
||||
return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
|
||||
index = int(index)
|
||||
if not 0 <= index < len(LogicNormal.youtube_dl_list):
|
||||
return LogicNormal.abort(ret, 3) # 인덱스 범위를 벗어남
|
||||
youtube_dl = LogicNormal.youtube_dl_list[index]
|
||||
if youtube_dl.key != key:
|
||||
return LogicNormal.abort(ret, 4) # 키가 일치하지 않음
|
||||
ret["status"] = youtube_dl.status.name
|
||||
if not youtube_dl.stop():
|
||||
return LogicNormal.abort(ret, 10) # 실패
|
||||
return jsonify(ret)
|
||||
|
||||
# 현재 상태를 반환하는 API
|
||||
elif sub == "status":
|
||||
index = request.values.get("index")
|
||||
key = request.values.get("key")
|
||||
ret = {
|
||||
"errorCode": 0,
|
||||
"status": None,
|
||||
"type": None,
|
||||
"start_time": None,
|
||||
"end_time": None,
|
||||
"temp_path": None,
|
||||
"save_path": None,
|
||||
}
|
||||
if None in (index, key):
|
||||
return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
|
||||
index = int(index)
|
||||
if not 0 <= index < len(LogicNormal.youtube_dl_list):
|
||||
return LogicNormal.abort(ret, 3) # 인덱스 범위를 벗어남
|
||||
youtube_dl = LogicNormal.youtube_dl_list[index]
|
||||
if youtube_dl.key != key:
|
||||
return LogicNormal.abort(ret, 4) # 키가 일치하지 않음
|
||||
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 youtube_dl.start_time is not None
|
||||
else None
|
||||
)
|
||||
ret["end_time"] = (
|
||||
youtube_dl.end_time.strftime("%Y-%m-%dT%H:%M:%S")
|
||||
if youtube_dl.end_time is not None
|
||||
else None
|
||||
)
|
||||
ret["temp_path"] = youtube_dl.temp_path
|
||||
ret["save_path"] = youtube_dl.save_path
|
||||
return jsonify(ret)
|
||||
except Exception as e:
|
||||
logger.error("Exception:%s", e)
|
||||
logger.error(traceback.format_exc())
|
||||
abort(500) # 500 에러(서버 오류)
|
||||
abort(404) # 404 에러(페이지 없음)
|
||||
|
||||
|
||||
#########################################################
|
||||
# socketio
|
||||
#########################################################
|
||||
def socketio_emit(cmd, data):
|
||||
socketio.emit(
|
||||
cmd, LogicNormal.get_data(data), namespace=f"/{package_name}", broadcast=True
|
||||
)
|
||||
77
setup.py
Normal file
77
setup.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Time : 2023/02/25 7:20 PM
|
||||
# @Author : yommi
|
||||
# @Site :
|
||||
# @File : setup.py
|
||||
# @Software: PyCharm
|
||||
# @Path : youtube-dl/setup.py
|
||||
|
||||
__menu = {
|
||||
"uri": __package__,
|
||||
"name": "유튜브 다운로더",
|
||||
"list": [
|
||||
{
|
||||
"uri": "basic",
|
||||
"name": "기본 처리",
|
||||
"list": [
|
||||
{
|
||||
"uri": "setting",
|
||||
"name": "설정",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
"uri": "basic/download",
|
||||
"name": "다운로드",
|
||||
},
|
||||
{
|
||||
"uri": "download",
|
||||
"name": "다운로드",
|
||||
"list": [
|
||||
{
|
||||
"uri": "basic",
|
||||
"name": "다운로드",
|
||||
},
|
||||
],
|
||||
},
|
||||
{"uri": "thumbnail", "name": "썸네일 다운로드"},
|
||||
{"uri": "sub", "name": "자막 다운로드"},
|
||||
{
|
||||
"uri": "manual",
|
||||
"name": "매뉴얼",
|
||||
"list": [
|
||||
{"uri": "README.md", "name": "README.md"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"uri": "log",
|
||||
"name": "로그",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
setting = {
|
||||
"filepath": __file__,
|
||||
"use_db": True,
|
||||
"use_default_setting": True,
|
||||
"home_module": None,
|
||||
"menu": __menu,
|
||||
"setting_menu": None,
|
||||
"default_route": "normal",
|
||||
}
|
||||
|
||||
|
||||
from plugin import *
|
||||
|
||||
P = create_plugin_instance(setting)
|
||||
P.youtube_dl_packages = ["youtube-dl", "yt-dlp", "youtube-dlc"]
|
||||
|
||||
try:
|
||||
from .mod_basic import ModuleBasic
|
||||
|
||||
P.set_module_list([ModuleBasic])
|
||||
except Exception as e:
|
||||
P.logger.error(f"Exception:{str(e)}")
|
||||
P.logger.error(traceback.format_exc())
|
||||
|
||||
logger = P.logger
|
||||
@@ -1,51 +1,69 @@
|
||||
'use strict';
|
||||
|
||||
const url = document.getElementById('url');
|
||||
const preset = document.getElementById('preset');
|
||||
const format = document.getElementById('format');
|
||||
const postprocessor = document.getElementById('postprocessor');
|
||||
const download_btn = document.getElementById('download_btn');
|
||||
|
||||
// 프리셋 변경
|
||||
preset.addEventListener('change', () => {
|
||||
if (preset.value !== '_custom') {
|
||||
format.value = preset.value;
|
||||
}
|
||||
});
|
||||
format.addEventListener('input', () => {
|
||||
preset.value = '_custom';
|
||||
});
|
||||
|
||||
// 후처리 변경
|
||||
postprocessor.addEventListener('change', () => {
|
||||
const select = postprocessor.selectedOptions[0];
|
||||
if (select.parentElement.label === '오디오 추출') {
|
||||
preset.value = 'bestaudio/best';
|
||||
format.value = preset.value;
|
||||
}
|
||||
});
|
||||
|
||||
// 다운로드
|
||||
download_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
if (!url.value.startsWith('http')) {
|
||||
notify('URL을 입력하세요.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/${package_name}/ajax/download`, {
|
||||
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');
|
||||
(() => {
|
||||
const post_ajax = (url, data) => {
|
||||
const loading = document.getElementById('loading');
|
||||
if (loading) {
|
||||
loading.style.display = 'block';
|
||||
}
|
||||
return fetch(`/${package_name}/ajax${url}`, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
body: new URLSearchParams(data),
|
||||
})
|
||||
.catch(() => {
|
||||
notify('다운로드 요청 실패', 'danger');
|
||||
});
|
||||
});
|
||||
.then((response) => response.json())
|
||||
.then((ret) => {
|
||||
if (ret.msg) {
|
||||
notify(ret.msg, ret.ret);
|
||||
}
|
||||
return ret;
|
||||
})
|
||||
.catch(() => {
|
||||
notify('요청 실패', 'danger');
|
||||
})
|
||||
.finally(() => {
|
||||
if (loading) {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const url = document.getElementById('url');
|
||||
const preset = document.getElementById('preset');
|
||||
const format = document.getElementById('format');
|
||||
const postprocessor = document.getElementById('postprocessor');
|
||||
const download_btn = document.getElementById('download_btn');
|
||||
|
||||
// 프리셋 변경
|
||||
preset.addEventListener('change', () => {
|
||||
if (preset.value !== '_custom') {
|
||||
format.value = preset.value;
|
||||
}
|
||||
});
|
||||
format.addEventListener('input', () => {
|
||||
preset.value = '_custom';
|
||||
});
|
||||
|
||||
// 후처리 변경
|
||||
postprocessor.addEventListener('change', () => {
|
||||
const select = postprocessor.selectedOptions[0];
|
||||
if (select.parentElement.label === '오디오 추출') {
|
||||
preset.value = 'bestaudio/best';
|
||||
format.value = preset.value;
|
||||
}
|
||||
});
|
||||
|
||||
// 다운로드
|
||||
download_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
if (!url.value.startsWith('http')) {
|
||||
notify('URL을 입력하세요.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
post_ajax('/download', get_formdata('#download'));
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -1,152 +1,155 @@
|
||||
'use strict';
|
||||
|
||||
const all_stop_btn = document.getElementById('all_stop_btn');
|
||||
const list_tbody = document.getElementById('list_tbody');
|
||||
|
||||
// 소켓
|
||||
const socket = io.connect(`${location.origin}/${package_name}`);
|
||||
socket.on('add', (data) => {
|
||||
list_tbody.innerHTML += make_item(data);
|
||||
});
|
||||
socket.on('status', (data) => {
|
||||
status_html(data);
|
||||
});
|
||||
|
||||
// 목록 불러오기
|
||||
fetch(`/${package_name}/ajax/list`, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
let str = '';
|
||||
for (const item of data) {
|
||||
str += make_item(item);
|
||||
(() => {
|
||||
const post_ajax = (url, data) => {
|
||||
const loading = document.getElementById('loading');
|
||||
if (loading) {
|
||||
loading.style.display = 'block';
|
||||
}
|
||||
list_tbody.innerHTML = str;
|
||||
return fetch(`/${package_name}/ajax${url}`, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
body: new URLSearchParams(data),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((ret) => {
|
||||
if (ret.msg) {
|
||||
notify(ret.msg, ret.ret);
|
||||
}
|
||||
return ret;
|
||||
})
|
||||
.catch(() => {
|
||||
notify('요청 실패', 'danger');
|
||||
})
|
||||
.finally(() => {
|
||||
if (loading) {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const all_stop_btn = document.getElementById('all_stop_btn');
|
||||
const list_tbody = document.getElementById('list_tbody');
|
||||
|
||||
const get_item = (data) => {
|
||||
let str = `<td>${data.index + 1}</td>`;
|
||||
str += `<td>${data.plugin}</td>`;
|
||||
str += `<td>${data.start_time}</td>`;
|
||||
str += `<td>${data.extractor}</td>`;
|
||||
str += `<td>${data.title}</td>`;
|
||||
str += `<td>${data.status_ko}</td>`;
|
||||
let visi = 'hidden';
|
||||
if (parseInt(data.percent) > 0 && data.status_str !== 'STOP') {
|
||||
visi = 'visible';
|
||||
}
|
||||
str += `<td><div class="progress"><div class="progress-bar" style="visibility: ${visi}; width: ${data.percent}%">${data.percent}%</div></div></td>`;
|
||||
str += `<td>${data.download_time}</td>`;
|
||||
str += '<td class="tableRowHoverOff">';
|
||||
if (
|
||||
data.status_str === 'START' ||
|
||||
data.status_str === 'DOWNLOADING' ||
|
||||
data.status_str === 'FINISHED'
|
||||
) {
|
||||
str += `<button class="align-middle btn btn-outline-danger btn-sm youtubeDl-stop" data-index="${data.index}">중지</button>`;
|
||||
}
|
||||
str += '</td>';
|
||||
return str;
|
||||
};
|
||||
|
||||
const info_html = (left, right, option) => {
|
||||
let str = '<div class="row">';
|
||||
const link = left === 'URL' || left === '업로더';
|
||||
str += '<div class="col-sm-2">';
|
||||
str += `<b>${left}</b>`;
|
||||
str += '</div>';
|
||||
str += '<div class="col-sm-10">';
|
||||
str += '<div class="input-group col-sm-9">';
|
||||
str += '<span class="text-left info-padding">';
|
||||
if (link) {
|
||||
str += `<a href="${option}" target="_blank">`;
|
||||
}
|
||||
str += right;
|
||||
if (link) {
|
||||
str += '</a>';
|
||||
}
|
||||
str += '</span></div></div></div>';
|
||||
return str;
|
||||
};
|
||||
|
||||
const get_detail = (data) => {
|
||||
let str = info_html('URL', data.url, data.url);
|
||||
str += info_html('업로더', data.uploader, data.uploader_url);
|
||||
str += info_html('임시폴더', data.temp_path);
|
||||
str += info_html('저장폴더', data.save_path);
|
||||
str += info_html('종료시간', data.end_time);
|
||||
if (data.status_str === 'DOWNLOADING') {
|
||||
str += info_html('', '<b>현재 다운로드 중인 파일에 대한 정보</b>');
|
||||
str += info_html('파일명', data.filename);
|
||||
str += info_html(
|
||||
'진행률(current/total)',
|
||||
`${data.percent}% (${data.downloaded_bytes_str} / ${data.total_bytes_str})`
|
||||
);
|
||||
str += info_html('남은 시간', `${data.eta}초`);
|
||||
str += info_html('다운 속도', data.speed_str);
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
const make_item = (data) => {
|
||||
let str = `<tr id="item_${data.index}" class="cursor-pointer" aria-expanded="true" data-toggle="collapse" data-target="#collapse_${data.index}">`;
|
||||
str += get_item(data);
|
||||
str += '</tr>';
|
||||
str += `<tr id="collapse_${data.index}" class="collapse tableRowHoverOff">`;
|
||||
str += '<td colspan="9">';
|
||||
str += `<div id="detail_${data.index}">`;
|
||||
str += get_detail(data);
|
||||
str += '</div>';
|
||||
str += '</td>';
|
||||
str += '</tr>';
|
||||
return str;
|
||||
};
|
||||
|
||||
const status_html = (data) => {
|
||||
document.getElementById(`item_${data.index}`).innerHTML = get_item(data);
|
||||
document.getElementById(`detail_${data.index}`).innerHTML =
|
||||
get_detail(data);
|
||||
};
|
||||
|
||||
// 소켓
|
||||
const socket = io.connect(`${location.origin}/${package_name}`);
|
||||
socket.on('add', (data) => {
|
||||
list_tbody.innerHTML += make_item(data);
|
||||
});
|
||||
socket.on('status', (data) => {
|
||||
status_html(data);
|
||||
});
|
||||
|
||||
// 전체 중지
|
||||
all_stop_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
fetch(`/${package_name}/ajax/all_stop`, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(() => {
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
const reload_list = async () => {
|
||||
const { data } = await post_ajax('/list');
|
||||
list_tbody.innerHTML = data.map((item) => make_item(item)).join('');
|
||||
};
|
||||
|
||||
// 중지
|
||||
list_tbody.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
const target = event.target;
|
||||
if (!target.classList.contains('youtubeDl-stop')) {
|
||||
return;
|
||||
}
|
||||
fetch(`/${package_name}/ajax/stop`, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
// 전체 중지
|
||||
all_stop_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
post_ajax('/all_stop').then(reload_list);
|
||||
});
|
||||
|
||||
// 중지
|
||||
list_tbody.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
const target = event.target;
|
||||
if (!target.classList.contains('youtubeDl-stop')) {
|
||||
return;
|
||||
}
|
||||
post_ajax('/stop', {
|
||||
index: target.dataset.index,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(() => {
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
}).then(reload_list);
|
||||
});
|
||||
|
||||
function make_item(data) {
|
||||
let str = `<tr id="item_${data.index}" class="cursor-pointer" aria-expanded="true" data-toggle="collapse" data-target="#collapse_${data.index}">`;
|
||||
str += get_item(data);
|
||||
str += '</tr>';
|
||||
str += `<tr id="collapse_${data.index}" class="collapse tableRowHoverOff">`;
|
||||
str += '<td colspan="9">';
|
||||
str += `<div id="detail_${data.index}">`;
|
||||
str += get_detail(data);
|
||||
str += '</div>';
|
||||
str += '</td>';
|
||||
str += '</tr>';
|
||||
return str;
|
||||
}
|
||||
|
||||
function get_item(data) {
|
||||
let str = `<td>${data.index + 1}</td>`;
|
||||
str += `<td>${data.plugin}</td>`;
|
||||
str += `<td>${data.start_time}</td>`;
|
||||
str += `<td>${data.extractor}</td>`;
|
||||
str += `<td>${data.title}</td>`;
|
||||
str += `<td>${data.status_ko}</td>`;
|
||||
let visi = 'hidden';
|
||||
if (parseInt(data.percent) > 0 && data.status_str !== 'STOP') {
|
||||
visi = 'visible';
|
||||
}
|
||||
str += `<td><div class="progress"><div class="progress-bar" style="visibility: ${visi}; width: ${data.percent}%">${data.percent}%</div></div></td>`;
|
||||
str += `<td>${data.download_time}</td>`;
|
||||
str += '<td class="tableRowHoverOff">';
|
||||
if (
|
||||
data.status_str === 'START' ||
|
||||
data.status_str === 'DOWNLOADING' ||
|
||||
data.status_str === 'FINISHED'
|
||||
) {
|
||||
str += `<button class="align-middle btn btn-outline-danger btn-sm youtubeDl-stop" data-index="${data.index}">중지</button>`;
|
||||
}
|
||||
str += '</td>';
|
||||
return str;
|
||||
}
|
||||
|
||||
function get_detail(data) {
|
||||
let str = info_html('URL', data.url, data.url);
|
||||
str += info_html('업로더', data.uploader, data.uploader_url);
|
||||
str += info_html('임시폴더', data.temp_path);
|
||||
str += info_html('저장폴더', data.save_path);
|
||||
str += info_html('종료시간', data.end_time);
|
||||
if (data.status_str === 'DOWNLOADING') {
|
||||
str += info_html('', '<b>현재 다운로드 중인 파일에 대한 정보</b>');
|
||||
str += info_html('파일명', data.filename);
|
||||
str += info_html(
|
||||
'진행률(current/total)',
|
||||
`${data.percent}% (${data.downloaded_bytes_str} / ${data.total_bytes_str})`
|
||||
);
|
||||
str += info_html('남은 시간', `${data.eta}초`);
|
||||
str += info_html('다운 속도', data.speed_str);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
function info_html(left, right, option) {
|
||||
let str = '<div class="row">';
|
||||
const link = left === 'URL' || left === '업로더';
|
||||
str += '<div class="col-sm-2">';
|
||||
str += `<b>${left}</b>`;
|
||||
str += '</div>';
|
||||
str += '<div class="col-sm-10">';
|
||||
str += '<div class="input-group col-sm-9">';
|
||||
str += '<span class="text-left info-padding">';
|
||||
if (link) {
|
||||
str += `<a href="${option}" target="_blank">`;
|
||||
}
|
||||
str += right;
|
||||
if (link) {
|
||||
str += '</a>';
|
||||
}
|
||||
str += '</span></div></div></div>';
|
||||
return str;
|
||||
}
|
||||
|
||||
function status_html(data) {
|
||||
document.getElementById(`item_${data.index}`).innerHTML = get_item(data);
|
||||
document.getElementById(`detail_${data.index}`).innerHTML = get_detail(data);
|
||||
}
|
||||
// 목록 불러오기
|
||||
reload_list();
|
||||
})();
|
||||
|
||||
@@ -1,74 +1,94 @@
|
||||
'use strict';
|
||||
|
||||
const ffmpeg_path = document.getElementById('ffmpeg_path');
|
||||
const ffmpeg_version_btn = document.getElementById('ffmpeg_version_btn');
|
||||
const ffmpeg_path_btn = document.getElementById('ffmpeg_path_btn');
|
||||
const temp_path = document.getElementById('temp_path');
|
||||
const temp_path_btn = document.getElementById('temp_path_btn');
|
||||
const save_path = document.getElementById('save_path');
|
||||
const save_path_btn = document.getElementById('save_path_btn');
|
||||
const modal_title = document.getElementById('modal_title');
|
||||
const modal_body = document.getElementById('modal_body');
|
||||
(() => {
|
||||
const post_ajax = (url, data) => {
|
||||
const loading = document.getElementById('loading');
|
||||
if (loading) {
|
||||
loading.style.display = 'block';
|
||||
}
|
||||
return fetch(`/${package_name}/ajax${url}`, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
body: new URLSearchParams(data),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((ret) => {
|
||||
if (ret.msg) {
|
||||
notify(ret.msg, ret.ret);
|
||||
}
|
||||
return ret;
|
||||
})
|
||||
.catch(() => {
|
||||
notify('요청 실패', 'danger');
|
||||
})
|
||||
.finally(() => {
|
||||
if (loading) {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// FFmpeg 버전확인
|
||||
ffmpeg_version_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
let ffmpeg = ffmpeg_path.value;
|
||||
if (ffmpeg.length === 0) {
|
||||
ffmpeg = 'ffmpeg';
|
||||
}
|
||||
const ffmpeg_path = document.getElementById('ffmpeg_path');
|
||||
const ffmpeg_version_btn = document.getElementById('ffmpeg_version_btn');
|
||||
const ffmpeg_path_btn = document.getElementById('ffmpeg_path_btn');
|
||||
const temp_path = document.getElementById('temp_path');
|
||||
const temp_path_btn = document.getElementById('temp_path_btn');
|
||||
const save_path = document.getElementById('save_path');
|
||||
const save_path_btn = document.getElementById('save_path_btn');
|
||||
const modal_title = document.getElementById('modal_title');
|
||||
const modal_body = document.getElementById('modal_body');
|
||||
|
||||
fetch(`/${package_name}/ajax/ffmpeg_version`, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
// FFmpeg 버전확인
|
||||
ffmpeg_version_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
let ffmpeg = ffmpeg_path.value;
|
||||
if (ffmpeg.length === 0) {
|
||||
ffmpeg = 'ffmpeg';
|
||||
}
|
||||
|
||||
post_ajax('/ffmpeg_version', {
|
||||
path: ffmpeg,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
}).then(({ data }) => {
|
||||
modal_title.innerHTML = `${ffmpeg} -version`;
|
||||
modal_body.innerHTML = data;
|
||||
$('#large_modal').modal();
|
||||
})
|
||||
.catch(() => {
|
||||
notify('버전확인 실패', 'danger');
|
||||
});
|
||||
});
|
||||
|
||||
// FFmpeg 파일 선택
|
||||
ffmpeg_path_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
m_select_local_file_modal('실행 파일 선택', '/', false, (result) => {
|
||||
ffmpeg_path.value = result;
|
||||
});
|
||||
});
|
||||
|
||||
// 임시 폴더 경로 선택
|
||||
temp_path_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
m_select_local_file_modal(
|
||||
'저장 경로 선택',
|
||||
temp_path.value,
|
||||
true,
|
||||
(result) => {
|
||||
temp_path.value = result;
|
||||
}
|
||||
);
|
||||
});
|
||||
// FFmpeg 파일 선택
|
||||
ffmpeg_path_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
m_select_local_file_modal('실행 파일 선택', '/', false, (result) => {
|
||||
ffmpeg_path.value = result;
|
||||
});
|
||||
});
|
||||
|
||||
// 저장 폴더 경로 선택
|
||||
save_path_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
m_select_local_file_modal(
|
||||
'저장 경로 선택',
|
||||
save_path.value,
|
||||
true,
|
||||
(result) => {
|
||||
save_path.value = result;
|
||||
}
|
||||
);
|
||||
});
|
||||
// 임시 폴더 경로 선택
|
||||
temp_path_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
m_select_local_file_modal(
|
||||
'저장 경로 선택',
|
||||
temp_path.value,
|
||||
true,
|
||||
(result) => {
|
||||
temp_path.value = result;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// 저장 폴더 경로 선택
|
||||
save_path_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
m_select_local_file_modal(
|
||||
'저장 경로 선택',
|
||||
save_path.value,
|
||||
true,
|
||||
(result) => {
|
||||
save_path.value = result;
|
||||
}
|
||||
);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -1,34 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
const url = document.getElementById('url');
|
||||
const download_btn = document.getElementById('download_btn');
|
||||
|
||||
// 모든 자막 다운로드
|
||||
$('#all_subs').change(() => {
|
||||
use_collapse('all_subs', true);
|
||||
});
|
||||
|
||||
// 다운로드
|
||||
download_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
if (!url.value.startsWith('http')) {
|
||||
notify('URL을 입력하세요.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/${package_name}/ajax/sub`, {
|
||||
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');
|
||||
(() => {
|
||||
const post_ajax = (url, data) => {
|
||||
const loading = document.getElementById('loading');
|
||||
if (loading) {
|
||||
loading.style.display = 'block';
|
||||
}
|
||||
return fetch(`/${package_name}/ajax${url}`, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
body: new URLSearchParams(data),
|
||||
})
|
||||
.catch(() => {
|
||||
notify('다운로드 요청 실패', 'danger');
|
||||
});
|
||||
});
|
||||
.then((response) => response.json())
|
||||
.then((ret) => {
|
||||
if (ret.msg) {
|
||||
notify(ret.msg, ret.ret);
|
||||
}
|
||||
return ret;
|
||||
})
|
||||
.catch(() => {
|
||||
notify('요청 실패', 'danger');
|
||||
})
|
||||
.finally(() => {
|
||||
if (loading) {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const url = document.getElementById('url');
|
||||
const download_btn = document.getElementById('download_btn');
|
||||
|
||||
// 모든 자막 다운로드
|
||||
$('#all_subs').change(() => {
|
||||
use_collapse('all_subs', true);
|
||||
});
|
||||
|
||||
// 다운로드
|
||||
download_btn.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
if (!url.value.startsWith('http')) {
|
||||
notify('URL을 입력하세요.', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
post_ajax('/sub', get_formdata('#download'));
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -1,29 +1,47 @@
|
||||
'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');
|
||||
(() => {
|
||||
const post_ajax = (url, data) => {
|
||||
const loading = document.getElementById('loading');
|
||||
if (loading) {
|
||||
loading.style.display = 'block';
|
||||
}
|
||||
return fetch(`/${package_name}/ajax${url}`, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
},
|
||||
body: new URLSearchParams(data),
|
||||
})
|
||||
.catch(() => {
|
||||
notify('다운로드 요청 실패', 'danger');
|
||||
});
|
||||
});
|
||||
.then((response) => response.json())
|
||||
.then((ret) => {
|
||||
if (ret.msg) {
|
||||
notify(ret.msg, ret.ret);
|
||||
}
|
||||
return ret;
|
||||
})
|
||||
.catch(() => {
|
||||
notify('요청 실패', 'danger');
|
||||
})
|
||||
.finally(() => {
|
||||
if (loading) {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
post_ajax('/thumbnail', get_formdata('#download'));
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -31,19 +31,19 @@
|
||||
|
||||
{% 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_select('preset', '동영상 포맷 프리셋', arg['preset_list'], col='3') }}
|
||||
{{ macros.setting_input_text('format', '동영상 포맷', desc=['포맷 지정은 https://github.com/ytdl-org/youtube-dl/#format-selection 참고', '빈칸으로 두면 최고 화질로 다운로드합니다.']) }}
|
||||
{{ my_setting_select('postprocessor', '후처리', arg['postprocessor_list'], col='3', desc='다운로드 후 FFmpeg로 후처리합니다.') }}
|
||||
{{ macros.setting_button([['download_btn', '다운로드']]) }}
|
||||
</form>
|
||||
<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_select('preset', '동영상 포맷 프리셋', arg['preset_list'], col='3') }}
|
||||
{{ macros.setting_input_text('format', '동영상 포맷', desc=['포맷 지정은 https://github.com/ytdl-org/youtube-dl/#format-selection 참고', '빈칸으로 두면 최고 화질로 다운로드합니다.']) }}
|
||||
{{ my_setting_select('postprocessor', '후처리', arg['postprocessor_list'], col='3', desc='다운로드 후 FFmpeg로 후처리합니다.') }}
|
||||
{{ macros.setting_buttons([['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>
|
||||
<script>
|
||||
"use strict";
|
||||
const package_name = '{{ arg["package_name"] }}';
|
||||
</script>
|
||||
<script src="{{ url_for('.static', filename='%s.js' % arg['template_name']) }}?ver={{ arg['package_version'] }}"></script>
|
||||
|
||||
{% endblock %}
|
||||
34
templates/youtube-dl_basic_setting.html
Normal file
34
templates/youtube-dl_basic_setting.html
Normal file
@@ -0,0 +1,34 @@
|
||||
{% extends "base.html" %} {% block content %}
|
||||
{{ macros.m_button_group([['globalSettingSaveBtn', '설정 저장'], ['globalOneExecuteBtn', '1회 실행'], ['globalImmediatelyExecuteBtn', '즉시 실행']])}}
|
||||
{{ macros.m_row_start('5') }}
|
||||
{{ macros.m_row_end() }}
|
||||
|
||||
<form id="setting">
|
||||
{{ macros.setting_radio_with_value('youtube_dl_package', 'youtube-dl', arg['package_list'],
|
||||
value=arg['youtube_dl_package'], desc='사용할 youtube-dl 패키지를 선택합니다. 설정 저장 후
|
||||
재시작이 필요합니다.') }}
|
||||
{{ macros.setting_input_text('youtube_dl_version', 'youtube-dl 버전',
|
||||
value=arg['youtube_dl_version'], disabled=True) }}
|
||||
{{
|
||||
macros.setting_input_text_and_buttons('ffmpeg_path', 'FFmpeg 경로', [['ffmpeg_version_btn',
|
||||
'버전확인'], ['ffmpeg_path_btn', '파일 선택']], value=arg['ffmpeg_path'], placeholder='ffmpeg',
|
||||
desc='SJVA에 내장된 버전 말고 원하는 버전을 사용할 수 있습니다.') }} {{
|
||||
macros.setting_input_text_and_buttons('temp_path', '임시 폴더', [['temp_path_btn', '경로 선택']],
|
||||
value=arg['temp_path'], desc='다운로드 파일이 임시로 저장될 폴더입니다.') }} {{
|
||||
macros.setting_input_text_and_buttons('save_path', '저장 폴더', [['save_path_btn', '경로 선택']],
|
||||
value=arg['save_path'], desc='정상적으로 완료된 파일이 이동할 폴더입니다.') }} {{
|
||||
macros.setting_input_text('default_filename', '기본 파일명', value=arg['default_filename'],
|
||||
placeholder=arg['DEFAULT_FILENAME'], desc='템플릿 규칙은
|
||||
https://github.com/ytdl-org/youtube-dl/#output-template 참고') }} {{
|
||||
macros.setting_input_text('proxy', '프록시', value=arg['proxy'], desc=['HTTP/HTTPS/SOCKS를
|
||||
지원합니다. 예) socks5://127.0.0.1:1080/', '빈칸으로 두면 프록시를 사용하지 않습니다.']) }}
|
||||
{# {{ macros.setting_button([['global_setting_save_btn', '저장']]) }}#}
|
||||
</form>
|
||||
|
||||
<script>
|
||||
"use strict"
|
||||
const package_name = '{{ arg["package_name"] }}'
|
||||
</script>
|
||||
<script src="{{ url_for('.static', filename='%s.js' % arg['template_name']) }}?ver={{ arg['package_version'] }}"></script>
|
||||
|
||||
{% endblock %}
|
||||
49
templates/youtube-dl_download_basic.html
Normal file
49
templates/youtube-dl_download_basic.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% macro my_setting_select(id, title, options, col='9', desc=None, value=None) %}
|
||||
{{ macros.setting_top(title) }}
|
||||
<div class="input-group col-sm-{{ col }}">
|
||||
<select id="{{ id }}" name="{{ id }}" class="form-control form-control-sm">
|
||||
{% set ns = namespace(optgroup=none) %}
|
||||
{% for item in options %}
|
||||
{% if ns.optgroup != item[2] %}
|
||||
{% if ns.optgroup is not none %}
|
||||
</optgroup>
|
||||
{% endif %}
|
||||
{% if item[2] is not none %}
|
||||
<optgroup label="{{ item[2] }}">
|
||||
{% endif %}
|
||||
{% set ns.optgroup = item[2] %}
|
||||
{% endif %}
|
||||
{% if value is not none and value == item[0] %}
|
||||
<option value="{{ item[0] }}" selected>{{ item[1] }}</option>
|
||||
{% else %}
|
||||
<option value="{{ item[0] }}">{{ item[1] }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if ns.optgroup is not none %}
|
||||
</optgroup>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
{{ macros.setting_bottom(desc) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% 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_select('preset', '동영상 포맷 프리셋', arg['preset_list'], col='3') }}
|
||||
{{ macros.setting_input_text('format', '동영상 포맷', desc=['포맷 지정은 https://github.com/ytdl-org/youtube-dl/#format-selection 참고', '빈칸으로 두면 최고 화질로 다운로드합니다.']) }}
|
||||
{{ my_setting_select('postprocessor', '후처리', arg['postprocessor_list'], col='3', desc='다운로드 후 FFmpeg로 후처리합니다.') }}
|
||||
{{ 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']) }}?ver={{ arg['package_version'] }}"></script>
|
||||
|
||||
{% endblock %}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ url_for('.static', filename='%s.css' % arg['template_name']) }}"
|
||||
href="{{ url_for('.static', filename='%s.css' % arg['template_name']) }}?ver={{ arg['package_version'] }}"
|
||||
/>
|
||||
|
||||
{{ macros.m_row_start() }} {{ macros.m_button('all_stop_btn', '전체 중지') }} {{
|
||||
@@ -30,6 +30,6 @@ macros.m_row_end() }}
|
||||
'use strict';
|
||||
const package_name = '{{ arg["package_name"] }}';
|
||||
</script>
|
||||
<script src="{{ url_for('.static', filename='%s.js' % arg['template_name']) }}"></script>
|
||||
<script src="{{ url_for('.static', filename='%s.js' % arg['template_name']) }}?ver={{ arg['package_version'] }}"></script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -29,6 +29,6 @@
|
||||
'use strict';
|
||||
const package_name = '{{ arg["package_name"] }}';
|
||||
</script>
|
||||
<script src="{{ url_for('.static', filename='%s.js' % arg['template_name']) }}"></script>
|
||||
<script src="{{ url_for('.static', filename='%s.js' % arg['template_name']) }}?ver={{ arg['package_version'] }}"></script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
'use strict';
|
||||
const package_name = '{{ arg["package_name"] }}';
|
||||
</script>
|
||||
<script src="{{ url_for('.static', filename='%s.js' % arg['template_name']) }}"></script>
|
||||
<script src="{{ url_for('.static', filename='%s.js' % arg['template_name']) }}?ver={{ arg['package_version'] }}"></script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
'use strict';
|
||||
const package_name = '{{ arg["package_name"] }}';
|
||||
</script>
|
||||
<script src="{{ url_for('.static', filename='%s.js' % arg['template_name']) }}"></script>
|
||||
<script src="{{ url_for('.static', filename='%s.js' % arg['template_name']) }}?ver={{ arg['package_version'] }}"></script>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user