Compare commits
10 Commits
3202fd2fb9
...
1167e60c36
| Author | SHA1 | Date | |
|---|---|---|---|
| 1167e60c36 | |||
|
|
51ef1ee459 | ||
|
|
3410ddd73c | ||
|
|
26e4f96d49 | ||
|
|
3ef17ad0b3 | ||
|
|
44b792bc28 | ||
|
|
3d1e12d081 | ||
|
|
8cd3c769b0 | ||
|
|
28dcded7ce | ||
|
|
c74ad4bc78 |
28
README.md
28
README.md
@@ -15,7 +15,7 @@ SJVA에서 "시스템 → 플러그인 → 플러그인 수동 설치" 칸에
|
|||||||
API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로드를 요청할 수 있습니다.
|
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가 우선 지원됩니다.
|
설정에서 취향껏 골라서 사용하시면 되며 youtube-dl가 우선 지원됩니다.
|
||||||
|
|
||||||
## API
|
## API
|
||||||
@@ -93,8 +93,10 @@ API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로
|
|||||||
| `archive` | 다운로드한 동영상의 ID를 기록할 파일 경로. 파일이 이미 있으면 이미 다운로드한 동영상은 다운로드 하지 않음. 미지정 시 기록하지 않음 | X | String |
|
| `archive` | 다운로드한 동영상의 ID를 기록할 파일 경로. 파일이 이미 있으면 이미 다운로드한 동영상은 다운로드 하지 않음. 미지정 시 기록하지 않음 | X | String |
|
||||||
| `start` | 다운로드 준비 후 바로 다운로드를 시작할지 여부. 기본값: `false` | X | Boolean |
|
| `start` | 다운로드 준비 후 바로 다운로드를 시작할지 여부. 기본값: `false` | X | Boolean |
|
||||||
| `cookiefile` | 다운로드 시 필요한 쿠키 파일 경로 | X | String |
|
| `cookiefile` | 다운로드 시 필요한 쿠키 파일 경로 | X | String |
|
||||||
|
| `headers` | 다운로드 시 사용할 HTTP 헤더 정보 | X | String |
|
||||||
|
|
||||||
`dateafter` 키에 넣을 수 있는 날짜는 `"YYYYMMDD"` 또는 `"(now|today)[+-][0-9](day|week|month|year)(s)?"` 형식의 문자열입니다.
|
`dateafter` 키에 넣을 수 있는 날짜는 `"YYYYMMDD"` 또는 `"(now|today)[+-][0-9](day|week|month|year)(s)?"` 형식의 문자열입니다.
|
||||||
|
`headers` 키에 넣는 값은 `json` 형식의 문자열입니다.
|
||||||
|
|
||||||
`playlist` 키에 넣을 수 있는 값은 `"1-3,7,10-13"` 형식의 범위 또는 `"reverse"`, `"random"`입니다.
|
`playlist` 키에 넣을 수 있는 값은 `"1-3,7,10-13"` 형식의 범위 또는 `"reverse"`, `"random"`입니다.
|
||||||
범위를 넣으면 플레이리스트에서 선택한 것만 다운로드합니다.
|
범위를 넣으면 플레이리스트에서 선택한 것만 다운로드합니다.
|
||||||
@@ -127,8 +129,10 @@ API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로
|
|||||||
| `archive` | 다운로드한 동영상의 ID를 기록할 파일 경로. 파일이 이미 있으면 이미 다운로드한 동영상은 다운로드 하지 않음. 미지정 시 기록하지 않음 | X | String |
|
| `archive` | 다운로드한 동영상의 ID를 기록할 파일 경로. 파일이 이미 있으면 이미 다운로드한 동영상은 다운로드 하지 않음. 미지정 시 기록하지 않음 | X | String |
|
||||||
| `start` | 다운로드 준비 후 바로 다운로드를 시작할지 여부. 기본값: `false` | X | Boolean |
|
| `start` | 다운로드 준비 후 바로 다운로드를 시작할지 여부. 기본값: `false` | X | Boolean |
|
||||||
| `cookiefile` | 다운로드 시 필요한 쿠키 파일 경로 | X | String |
|
| `cookiefile` | 다운로드 시 필요한 쿠키 파일 경로 | X | String |
|
||||||
|
| `headers` | 다운로드 시 사용할 HTTP 헤더 정보 | X | String |
|
||||||
|
|
||||||
`dateafter` 키에 넣을 수 있는 날짜는 `"YYYYMMDD"` 또는 `"(now|today)[+-][0-9](day|week|month|year)(s)?"` 형식의 문자열입니다.
|
`dateafter` 키에 넣을 수 있는 날짜는 `"YYYYMMDD"` 또는 `"(now|today)[+-][0-9](day|week|month|year)(s)?"` 형식의 문자열입니다.
|
||||||
|
`headers` 키에 넣는 값은 `json` 형식의 문자열입니다.
|
||||||
|
|
||||||
`playlist` 키에 넣을 수 있는 값은 `"1-3,7,10-13"` 형식의 범위 또는 `"reverse"`, `"random"`입니다.
|
`playlist` 키에 넣을 수 있는 값은 `"1-3,7,10-13"` 형식의 범위 또는 `"reverse"`, `"random"`입니다.
|
||||||
범위를 넣으면 플레이리스트에서 선택한 것만 다운로드합니다.
|
범위를 넣으면 플레이리스트에서 선택한 것만 다운로드합니다.
|
||||||
@@ -163,8 +167,10 @@ API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로
|
|||||||
| `archive` | 다운로드한 동영상의 ID를 기록할 파일 경로. 파일이 이미 있으면 이미 다운로드한 동영상은 다운로드 하지 않음. 미지정 시 기록하지 않음 | X | String |
|
| `archive` | 다운로드한 동영상의 ID를 기록할 파일 경로. 파일이 이미 있으면 이미 다운로드한 동영상은 다운로드 하지 않음. 미지정 시 기록하지 않음 | X | String |
|
||||||
| `start` | 다운로드 준비 후 바로 다운로드를 시작할지 여부. 기본값: `false` | X | Boolean |
|
| `start` | 다운로드 준비 후 바로 다운로드를 시작할지 여부. 기본값: `false` | X | Boolean |
|
||||||
| `cookiefile` | 다운로드 시 필요한 쿠키 파일 경로 | X | String |
|
| `cookiefile` | 다운로드 시 필요한 쿠키 파일 경로 | X | String |
|
||||||
|
| `headers` | 다운로드 시 사용할 HTTP 헤더 정보 | X | String |
|
||||||
|
|
||||||
`dateafter` 키에 넣을 수 있는 날짜는 `"YYYYMMDD"` 또는 `"(now|today)[+-][0-9](day|week|month|year)(s)?"` 형식의 문자열입니다.
|
`dateafter` 키에 넣을 수 있는 날짜는 `"YYYYMMDD"` 또는 `"(now|today)[+-][0-9](day|week|month|year)(s)?"` 형식의 문자열입니다.
|
||||||
|
`headers` 키에 넣는 값은 `json` 형식의 문자열입니다.
|
||||||
|
|
||||||
`playlist` 키에 넣을 수 있는 값은 `"1-3,7,10-13"` 형식의 범위 또는 `"reverse"`, `"random"`입니다.
|
`playlist` 키에 넣을 수 있는 값은 `"1-3,7,10-13"` 형식의 범위 또는 `"reverse"`, `"random"`입니다.
|
||||||
범위를 넣으면 플레이리스트에서 선택한 것만 다운로드합니다.
|
범위를 넣으면 플레이리스트에서 선택한 것만 다운로드합니다.
|
||||||
@@ -245,6 +251,20 @@ API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로
|
|||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
v4.0.1
|
||||||
|
|
||||||
|
- 다운로드 요청 오류 수정
|
||||||
|
|
||||||
|
v4.0.0
|
||||||
|
|
||||||
|
- 최신 플러그인 구조로 변경
|
||||||
|
- download, thumbnail, sub API에 headers 키 추가
|
||||||
|
http 헤더 수정이 필요한 경우 활용할 수 있습니다.
|
||||||
|
|
||||||
|
v3.1.1
|
||||||
|
|
||||||
|
- 디폴트 youtube_dl 패키지를 yt-dlp로 변경
|
||||||
|
|
||||||
v3.1.0
|
v3.1.0
|
||||||
|
|
||||||
- yt-dlp일 때 작동 안하는 문제 수정
|
- 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 traceback
|
||||||
|
import subprocess
|
||||||
|
import sqlite3
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from flask import jsonify
|
from flask import render_template, jsonify
|
||||||
|
|
||||||
from framework.logger import get_logger
|
|
||||||
|
|
||||||
from .my_youtube_dl import MyYoutubeDL, Status
|
from .my_youtube_dl import MyYoutubeDL, Status
|
||||||
|
|
||||||
package_name = __name__.split(".", maxsplit=1)[0]
|
logger = P.logger
|
||||||
logger = get_logger(package_name)
|
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 = []
|
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
|
@staticmethod
|
||||||
def get_youtube_dl_version():
|
def get_youtube_dl_version():
|
||||||
try:
|
try:
|
||||||
@@ -79,7 +277,7 @@ class LogicNormal(object):
|
|||||||
def get_postprocessor():
|
def get_postprocessor():
|
||||||
video_convertor = []
|
video_convertor = []
|
||||||
extract_audio = []
|
extract_audio = []
|
||||||
for i in LogicNormal.get_postprocessor_list():
|
for i in LogicMain.get_postprocessor_list():
|
||||||
if i[2] == "비디오 변환":
|
if i[2] == "비디오 변환":
|
||||||
video_convertor.append(i[0])
|
video_convertor.append(i[0])
|
||||||
elif i[2] == "오디오 추출":
|
elif i[2] == "오디오 추출":
|
||||||
@@ -131,12 +329,14 @@ class LogicNormal(object):
|
|||||||
opts["ffmpeg_location"] = kwagrs["ffmpeg_path"]
|
opts["ffmpeg_location"] = kwagrs["ffmpeg_path"]
|
||||||
if "cookiefile" in kwagrs and kwagrs["cookiefile"]:
|
if "cookiefile" in kwagrs and kwagrs["cookiefile"]:
|
||||||
opts["cookiefile"] = kwagrs["cookiefile"]
|
opts["cookiefile"] = kwagrs["cookiefile"]
|
||||||
|
if "headers" in kwagrs and kwagrs["headers"]:
|
||||||
|
opts["http_headers"] = kwagrs["headers"]
|
||||||
dateafter = kwagrs.get("dateafter")
|
dateafter = kwagrs.get("dateafter")
|
||||||
youtube_dl = MyYoutubeDL(
|
youtube_dl = MyYoutubeDL(
|
||||||
plugin, "video", url, filename, temp_path, save_path, opts, dateafter
|
plugin, "video", 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) # 리스트 추가
|
LogicMain.youtube_dl_list.append(youtube_dl) # 리스트 추가
|
||||||
return youtube_dl
|
return youtube_dl
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error("Exception:%s", error)
|
logger.error("Exception:%s", error)
|
||||||
@@ -175,6 +375,8 @@ class LogicNormal(object):
|
|||||||
opts["ffmpeg_location"] = kwagrs["ffmpeg_path"]
|
opts["ffmpeg_location"] = kwagrs["ffmpeg_path"]
|
||||||
if "cookiefile" in kwagrs and kwagrs["cookiefile"]:
|
if "cookiefile" in kwagrs and kwagrs["cookiefile"]:
|
||||||
opts["cookiefile"] = kwagrs["cookiefile"]
|
opts["cookiefile"] = kwagrs["cookiefile"]
|
||||||
|
if "headers" in kwagrs and kwagrs["headers"]:
|
||||||
|
opts["http_headers"] = kwagrs["headers"]
|
||||||
dateafter = kwagrs.get("dateafter")
|
dateafter = kwagrs.get("dateafter")
|
||||||
youtube_dl = MyYoutubeDL(
|
youtube_dl = MyYoutubeDL(
|
||||||
plugin,
|
plugin,
|
||||||
@@ -187,7 +389,7 @@ class LogicNormal(object):
|
|||||||
dateafter,
|
dateafter,
|
||||||
)
|
)
|
||||||
youtube_dl.key = kwagrs.get("key")
|
youtube_dl.key = kwagrs.get("key")
|
||||||
LogicNormal.youtube_dl_list.append(youtube_dl) # 리스트 추가
|
LogicMain.youtube_dl_list.append(youtube_dl) # 리스트 추가
|
||||||
return youtube_dl
|
return youtube_dl
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error("Exception:%s", error)
|
logger.error("Exception:%s", error)
|
||||||
@@ -230,12 +432,14 @@ class LogicNormal(object):
|
|||||||
opts["ffmpeg_location"] = kwagrs["ffmpeg_path"]
|
opts["ffmpeg_location"] = kwagrs["ffmpeg_path"]
|
||||||
if "cookiefile" in kwagrs and kwagrs["cookiefile"]:
|
if "cookiefile" in kwagrs and kwagrs["cookiefile"]:
|
||||||
opts["cookiefile"] = kwagrs["cookiefile"]
|
opts["cookiefile"] = kwagrs["cookiefile"]
|
||||||
|
if "headers" in kwagrs and kwagrs["headers"]:
|
||||||
|
opts["http_headers"] = kwagrs["headers"]
|
||||||
dateafter = kwagrs.get("dateafter")
|
dateafter = kwagrs.get("dateafter")
|
||||||
youtube_dl = MyYoutubeDL(
|
youtube_dl = MyYoutubeDL(
|
||||||
plugin, "subtitle", url, filename, temp_path, save_path, opts, dateafter
|
plugin, "subtitle", 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) # 리스트 추가
|
LogicMain.youtube_dl_list.append(youtube_dl) # 리스트 추가
|
||||||
return youtube_dl
|
return youtube_dl
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error("Exception:%s", error)
|
logger.error("Exception:%s", error)
|
||||||
@@ -284,9 +488,7 @@ class LogicNormal(object):
|
|||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
data["speed_str"] = (
|
data["speed_str"] = (
|
||||||
LogicNormal.human_readable_size(
|
LogicMain.human_readable_size(youtube_dl.progress_hooks["speed"], "/s")
|
||||||
youtube_dl.progress_hooks["speed"], "/s"
|
|
||||||
)
|
|
||||||
if youtube_dl.progress_hooks["speed"] is not None
|
if youtube_dl.progress_hooks["speed"] is not None
|
||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
@@ -303,19 +505,19 @@ class LogicNormal(object):
|
|||||||
youtube_dl.progress_hooks["downloaded_bytes"],
|
youtube_dl.progress_hooks["downloaded_bytes"],
|
||||||
youtube_dl.progress_hooks["total_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"]
|
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"]
|
youtube_dl.progress_hooks["total_bytes"]
|
||||||
)
|
)
|
||||||
data[
|
data[
|
||||||
"percent"
|
"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["start_time"] = youtube_dl.start_time.strftime("%m-%d %H:%M:%S")
|
||||||
data[
|
data[
|
||||||
"download_time"
|
"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
|
return data
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error("Exception:%s", error)
|
logger.error("Exception:%s", error)
|
||||||
@@ -335,6 +537,7 @@ class LogicNormal(object):
|
|||||||
return f"{size:.1f} YB{suffix}"
|
return f"{size:.1f} YB{suffix}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def abort(base, code):
|
def socketio_emit(cmd, data):
|
||||||
base["errorCode"] = code
|
socketio.emit(
|
||||||
return jsonify(base)
|
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
|
# -*- coding: utf-8 -*-
|
||||||
import traceback
|
# @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 .setup import *
|
||||||
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")
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ModelSetting(db.Model):
|
class ModelYoutubeDlItem(ModelBase):
|
||||||
__tablename__ = f"{package_name}_setting"
|
P = P
|
||||||
|
__tablename__ = "youtube_dl_item"
|
||||||
__table_args__ = {"mysql_collate": "utf8_general_ci"}
|
__table_args__ = {"mysql_collate": "utf8_general_ci"}
|
||||||
__bind_key__ = package_name
|
__bind_key__ = P.package_name
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
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 threading import Thread
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from framework.logger import get_logger
|
from framework import celery as celery_shutil
|
||||||
import framework.common.celery as celery_shutil
|
|
||||||
|
|
||||||
package_name = __name__.split(".", maxsplit=1)[0]
|
# from .plugin import Plugin
|
||||||
logger = get_logger(package_name)
|
|
||||||
|
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):
|
class Status(Enum):
|
||||||
@@ -25,11 +34,11 @@ class Status(Enum):
|
|||||||
COMPLETED = 6
|
COMPLETED = 6
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
str_list = ["??", "???", "?????", "??", "???", "??", "??"]
|
str_list = ["준비", "분석중", "다운로드중", "실패", "변환중", "중지", "완료"]
|
||||||
return str_list[self.value]
|
return str_list[self.value]
|
||||||
|
|
||||||
|
|
||||||
class MyYoutubeDL(object):
|
class MyYoutubeDL:
|
||||||
DEFAULT_FILENAME = "%(title)s-%(id)s.%(ext)s"
|
DEFAULT_FILENAME = "%(title)s-%(id)s.%(ext)s"
|
||||||
|
|
||||||
_index = 0
|
_index = 0
|
||||||
@@ -47,8 +56,6 @@ class MyYoutubeDL(object):
|
|||||||
datebefore=None,
|
datebefore=None,
|
||||||
):
|
):
|
||||||
# from youtube_dl.utils import DateRange
|
# from youtube_dl.utils import DateRange
|
||||||
from .plugin import youtube_dl_package
|
|
||||||
|
|
||||||
DateRange = __import__(
|
DateRange = __import__(
|
||||||
f"{youtube_dl_package}.utils", fromlist=["DateRange"]
|
f"{youtube_dl_package}.utils", fromlist=["DateRange"]
|
||||||
).DateRange
|
).DateRange
|
||||||
@@ -79,10 +86,10 @@ class MyYoutubeDL(object):
|
|||||||
self.end_time = None # 종료 시간
|
self.end_time = None # 종료 시간
|
||||||
# info_dict에서 얻는 정보
|
# info_dict에서 얻는 정보
|
||||||
self.info_dict = {
|
self.info_dict = {
|
||||||
"extractor": None, # ??
|
"extractor": None, # 타입
|
||||||
"title": None, # ??
|
"title": None, # 제목
|
||||||
"uploader": None, # ???
|
"uploader": None, # 업로더
|
||||||
"uploader_url": None, # ??? ??
|
"uploader_url": None, # 업로더 주소
|
||||||
}
|
}
|
||||||
# info_dict에서 얻는 정보(entries)
|
# info_dict에서 얻는 정보(entries)
|
||||||
# self.info_dict['playlist_index'] = None
|
# self.info_dict['playlist_index'] = None
|
||||||
@@ -91,10 +98,10 @@ class MyYoutubeDL(object):
|
|||||||
# self.info_dict['thumbnail'] = None # 썸네일
|
# self.info_dict['thumbnail'] = None # 썸네일
|
||||||
# progress_hooks에서 얻는 정보
|
# progress_hooks에서 얻는 정보
|
||||||
self.progress_hooks = {
|
self.progress_hooks = {
|
||||||
"downloaded_bytes": None, # ????? ??
|
"downloaded_bytes": None, # 다운로드한 크기
|
||||||
"total_bytes": None, # ?? ??
|
"total_bytes": None, # 전체 크기
|
||||||
"eta": None, # ?? ??(s)
|
"eta": None, # 예상 시간(s)
|
||||||
"speed": None, # ???? ??(bytes/s)
|
"speed": None, # 다운로드 속도(bytes/s)
|
||||||
}
|
}
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
@@ -106,8 +113,6 @@ class MyYoutubeDL(object):
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# import youtube_dl
|
# import youtube_dl
|
||||||
from .plugin import youtube_dl_package
|
|
||||||
|
|
||||||
youtube_dl = __import__(youtube_dl_package)
|
youtube_dl = __import__(youtube_dl_package)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -115,7 +120,10 @@ class MyYoutubeDL(object):
|
|||||||
self.status = Status.START
|
self.status = Status.START
|
||||||
# 동영상 정보 가져오기
|
# 동영상 정보 가져오기
|
||||||
info_dict = MyYoutubeDL.get_info_dict(
|
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:
|
if info_dict is None:
|
||||||
self.status = Status.ERROR
|
self.status = Status.ERROR
|
||||||
@@ -134,7 +142,9 @@ class MyYoutubeDL(object):
|
|||||||
}
|
}
|
||||||
ydl_opts.update(self.opts)
|
ydl_opts.update(self.opts)
|
||||||
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
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): # 다운로드 성공
|
if self.status in (Status.START, Status.FINISHED): # 다운로드 성공
|
||||||
for i in glob(self.temp_path + "/**/*", recursive=True):
|
for i in glob(self.temp_path + "/**/*", recursive=True):
|
||||||
path = i.replace(self.temp_path, self.save_path, 1)
|
path = i.replace(self.temp_path, self.save_path, 1)
|
||||||
@@ -144,9 +154,9 @@ class MyYoutubeDL(object):
|
|||||||
continue
|
continue
|
||||||
celery_shutil.move(i, path)
|
celery_shutil.move(i, path)
|
||||||
self.status = Status.COMPLETED
|
self.status = Status.COMPLETED
|
||||||
except Exception as e:
|
except Exception as error:
|
||||||
self.status = Status.ERROR
|
self.status = Status.ERROR
|
||||||
logger.error("Exception:%s", e)
|
logger.error("Exception:%s", error)
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
finally:
|
finally:
|
||||||
# 임시폴더 삭제
|
# 임시폴더 삭제
|
||||||
@@ -164,8 +174,6 @@ class MyYoutubeDL(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_version():
|
def get_version():
|
||||||
# from youtube_dl.version import __version__
|
# from youtube_dl.version import __version__
|
||||||
from .plugin import youtube_dl_package
|
|
||||||
|
|
||||||
__version__ = __import__(
|
__version__ = __import__(
|
||||||
f"{youtube_dl_package}.version", fromlist=["__version__"]
|
f"{youtube_dl_package}.version", fromlist=["__version__"]
|
||||||
).__version__
|
).__version__
|
||||||
@@ -173,10 +181,8 @@ class MyYoutubeDL(object):
|
|||||||
return __version__
|
return __version__
|
||||||
|
|
||||||
@staticmethod
|
@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
|
# import youtube_dl
|
||||||
from .plugin import youtube_dl_package
|
|
||||||
|
|
||||||
youtube_dl = __import__(youtube_dl_package)
|
youtube_dl = __import__(youtube_dl_package)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -185,27 +191,30 @@ class MyYoutubeDL(object):
|
|||||||
ydl_opts["proxy"] = proxy
|
ydl_opts["proxy"] = proxy
|
||||||
if cookiefile:
|
if cookiefile:
|
||||||
ydl_opts["cookiefile"] = cookiefile
|
ydl_opts["cookiefile"] = cookiefile
|
||||||
|
if http_headers:
|
||||||
|
ydl_opts["http_headers"] = http_headers
|
||||||
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
||||||
|
# info = ydl.extract_info(url, download=False)
|
||||||
info = ydl.extract_info(url, download=False)
|
info = ydl.extract_info(url, download=False)
|
||||||
except Exception as e:
|
except Exception as error:
|
||||||
logger.error("Exception:%s", e)
|
logger.error("Exception:%s", error)
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
return None
|
return None
|
||||||
return info
|
return ydl.sanitize_info(info)
|
||||||
|
|
||||||
def my_hook(self, d):
|
def my_hook(self, data):
|
||||||
if self.status != Status.STOP:
|
if self.status != Status.STOP:
|
||||||
self.status = {
|
self.status = {
|
||||||
"downloading": Status.DOWNLOADING,
|
"downloading": Status.DOWNLOADING,
|
||||||
"error": Status.ERROR,
|
"error": Status.ERROR,
|
||||||
"finished": Status.FINISHED, # ???? ??. ?? ??
|
"finished": Status.FINISHED, # 다운로드 완료. 변환 시작
|
||||||
}[d["status"]]
|
}[data["status"]]
|
||||||
if d["status"] != "error":
|
if data["status"] != "error":
|
||||||
self.filename = os.path.basename(d.get("filename"))
|
self.filename = os.path.basename(data.get("filename"))
|
||||||
self.progress_hooks["downloaded_bytes"] = d.get("downloaded_bytes")
|
self.progress_hooks["downloaded_bytes"] = data.get("downloaded_bytes")
|
||||||
self.progress_hooks["total_bytes"] = d.get("total_bytes")
|
self.progress_hooks["total_bytes"] = data.get("total_bytes")
|
||||||
self.progress_hooks["eta"] = d.get("eta")
|
self.progress_hooks["eta"] = data.get("eta")
|
||||||
self.progress_hooks["speed"] = d.get("speed")
|
self.progress_hooks["speed"] = data.get("speed")
|
||||||
|
|
||||||
def match_filter_func(self, info_dict):
|
def match_filter_func(self, info_dict):
|
||||||
self.info_dict["playlist_index"] = info_dict["playlist_index"]
|
self.info_dict["playlist_index"] = info_dict["playlist_index"]
|
||||||
@@ -220,15 +229,15 @@ class MyYoutubeDL(object):
|
|||||||
|
|
||||||
@status.setter
|
@status.setter
|
||||||
def status(self, value):
|
def status(self, value):
|
||||||
from .plugin import socketio_emit
|
from .main import LogicMain
|
||||||
|
|
||||||
self._status = value
|
self._status = value
|
||||||
socketio_emit("status", self)
|
LogicMain.socketio_emit("status", self)
|
||||||
|
|
||||||
|
|
||||||
class MyLogger(object):
|
class MyLogger:
|
||||||
def debug(self, msg):
|
def debug(self, msg):
|
||||||
if msg.find("\x1B") != -1 or msg.find("{") != -1:
|
if msg.find(" ETA ") != -1:
|
||||||
# 과도한 로그 방지
|
# 과도한 로그 방지
|
||||||
return
|
return
|
||||||
logger.debug(msg)
|
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';
|
'use strict';
|
||||||
|
|
||||||
const url = document.getElementById('url');
|
(() => {
|
||||||
const preset = document.getElementById('preset');
|
const post_ajax = (url, data) => {
|
||||||
const format = document.getElementById('format');
|
const loading = document.getElementById('loading');
|
||||||
const postprocessor = document.getElementById('postprocessor');
|
if (loading) {
|
||||||
const download_btn = document.getElementById('download_btn');
|
loading.style.display = 'block';
|
||||||
|
}
|
||||||
// 프리셋 변경
|
return fetch(`/${package_name}/ajax${url}`, {
|
||||||
preset.addEventListener('change', () => {
|
method: 'POST',
|
||||||
if (preset.value !== '_custom') {
|
cache: 'no-cache',
|
||||||
format.value = preset.value;
|
headers: {
|
||||||
}
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
});
|
},
|
||||||
format.addEventListener('input', () => {
|
body: new URLSearchParams(data),
|
||||||
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');
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.then((response) => response.json())
|
||||||
notify('다운로드 요청 실패', 'danger');
|
.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';
|
'use strict';
|
||||||
|
|
||||||
const all_stop_btn = document.getElementById('all_stop_btn');
|
(() => {
|
||||||
const list_tbody = document.getElementById('list_tbody');
|
const post_ajax = (url, data) => {
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
// 소켓
|
if (loading) {
|
||||||
const socket = io.connect(`${location.origin}/${package_name}`);
|
loading.style.display = 'block';
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 전체 중지
|
const reload_list = async () => {
|
||||||
all_stop_btn.addEventListener('click', (event) => {
|
const { data } = await post_ajax('/list');
|
||||||
event.preventDefault();
|
list_tbody.innerHTML = data.map((item) => make_item(item)).join('');
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 중지
|
// 전체 중지
|
||||||
list_tbody.addEventListener('click', (event) => {
|
all_stop_btn.addEventListener('click', (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const target = event.target;
|
post_ajax('/all_stop').then(reload_list);
|
||||||
if (!target.classList.contains('youtubeDl-stop')) {
|
});
|
||||||
return;
|
|
||||||
}
|
// 중지
|
||||||
fetch(`/${package_name}/ajax/stop`, {
|
list_tbody.addEventListener('click', (event) => {
|
||||||
method: 'POST',
|
event.preventDefault();
|
||||||
cache: 'no-cache',
|
const target = event.target;
|
||||||
headers: {
|
if (!target.classList.contains('youtubeDl-stop')) {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
return;
|
||||||
},
|
}
|
||||||
body: new URLSearchParams({
|
post_ajax('/stop', {
|
||||||
index: target.dataset.index,
|
index: target.dataset.index,
|
||||||
}),
|
}).then(reload_list);
|
||||||
})
|
});
|
||||||
.then((response) => response.json())
|
|
||||||
.then(() => {
|
|
||||||
location.reload();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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}">`;
|
reload_list();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,74 +1,94 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const ffmpeg_path = document.getElementById('ffmpeg_path');
|
(() => {
|
||||||
const ffmpeg_version_btn = document.getElementById('ffmpeg_version_btn');
|
const post_ajax = (url, data) => {
|
||||||
const ffmpeg_path_btn = document.getElementById('ffmpeg_path_btn');
|
const loading = document.getElementById('loading');
|
||||||
const temp_path = document.getElementById('temp_path');
|
if (loading) {
|
||||||
const temp_path_btn = document.getElementById('temp_path_btn');
|
loading.style.display = 'block';
|
||||||
const save_path = document.getElementById('save_path');
|
}
|
||||||
const save_path_btn = document.getElementById('save_path_btn');
|
return fetch(`/${package_name}/ajax${url}`, {
|
||||||
const modal_title = document.getElementById('modal_title');
|
method: 'POST',
|
||||||
const modal_body = document.getElementById('modal_body');
|
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 버전확인
|
const ffmpeg_path = document.getElementById('ffmpeg_path');
|
||||||
ffmpeg_version_btn.addEventListener('click', (event) => {
|
const ffmpeg_version_btn = document.getElementById('ffmpeg_version_btn');
|
||||||
event.preventDefault();
|
const ffmpeg_path_btn = document.getElementById('ffmpeg_path_btn');
|
||||||
let ffmpeg = ffmpeg_path.value;
|
const temp_path = document.getElementById('temp_path');
|
||||||
if (ffmpeg.length === 0) {
|
const temp_path_btn = document.getElementById('temp_path_btn');
|
||||||
ffmpeg = 'ffmpeg';
|
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`, {
|
// FFmpeg 버전확인
|
||||||
method: 'POST',
|
ffmpeg_version_btn.addEventListener('click', (event) => {
|
||||||
cache: 'no-cache',
|
event.preventDefault();
|
||||||
headers: {
|
let ffmpeg = ffmpeg_path.value;
|
||||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
if (ffmpeg.length === 0) {
|
||||||
},
|
ffmpeg = 'ffmpeg';
|
||||||
body: new URLSearchParams({
|
}
|
||||||
|
|
||||||
|
post_ajax('/ffmpeg_version', {
|
||||||
path: ffmpeg,
|
path: ffmpeg,
|
||||||
}),
|
}).then(({ data }) => {
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
modal_title.innerHTML = `${ffmpeg} -version`;
|
modal_title.innerHTML = `${ffmpeg} -version`;
|
||||||
modal_body.innerHTML = data;
|
modal_body.innerHTML = data;
|
||||||
$('#large_modal').modal();
|
$('#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;
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// 임시 폴더 경로 선택
|
// FFmpeg 파일 선택
|
||||||
temp_path_btn.addEventListener('click', (event) => {
|
ffmpeg_path_btn.addEventListener('click', (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
m_select_local_file_modal(
|
m_select_local_file_modal('실행 파일 선택', '/', false, (result) => {
|
||||||
'저장 경로 선택',
|
ffmpeg_path.value = result;
|
||||||
temp_path.value,
|
});
|
||||||
true,
|
});
|
||||||
(result) => {
|
|
||||||
temp_path.value = result;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 저장 폴더 경로 선택
|
// 임시 폴더 경로 선택
|
||||||
save_path_btn.addEventListener('click', (event) => {
|
temp_path_btn.addEventListener('click', (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
m_select_local_file_modal(
|
m_select_local_file_modal(
|
||||||
'저장 경로 선택',
|
'저장 경로 선택',
|
||||||
save_path.value,
|
temp_path.value,
|
||||||
true,
|
true,
|
||||||
(result) => {
|
(result) => {
|
||||||
save_path.value = 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';
|
'use strict';
|
||||||
|
|
||||||
const url = document.getElementById('url');
|
(() => {
|
||||||
const download_btn = document.getElementById('download_btn');
|
const post_ajax = (url, data) => {
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
// 모든 자막 다운로드
|
if (loading) {
|
||||||
$('#all_subs').change(() => {
|
loading.style.display = 'block';
|
||||||
use_collapse('all_subs', true);
|
}
|
||||||
});
|
return fetch(`/${package_name}/ajax${url}`, {
|
||||||
|
method: 'POST',
|
||||||
// 다운로드
|
cache: 'no-cache',
|
||||||
download_btn.addEventListener('click', (event) => {
|
headers: {
|
||||||
event.preventDefault();
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
if (!url.value.startsWith('http')) {
|
},
|
||||||
notify('URL을 입력하세요.', 'warning');
|
body: new URLSearchParams(data),
|
||||||
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');
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.then((response) => response.json())
|
||||||
notify('다운로드 요청 실패', 'danger');
|
.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';
|
'use strict';
|
||||||
|
|
||||||
const url = document.getElementById('url');
|
(() => {
|
||||||
const download_btn = document.getElementById('download_btn');
|
const post_ajax = (url, data) => {
|
||||||
|
const loading = document.getElementById('loading');
|
||||||
// 다운로드
|
if (loading) {
|
||||||
download_btn.addEventListener('click', (event) => {
|
loading.style.display = 'block';
|
||||||
event.preventDefault();
|
}
|
||||||
if (!url.value.startsWith('http')) {
|
return fetch(`/${package_name}/ajax${url}`, {
|
||||||
notify('URL을 입력하세요.', 'warning');
|
method: 'POST',
|
||||||
return;
|
cache: 'no-cache',
|
||||||
}
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||||
fetch(`/${package_name}/ajax/thumbnail`, {
|
},
|
||||||
method: 'POST',
|
body: new URLSearchParams(data),
|
||||||
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(() => {
|
.then((response) => response.json())
|
||||||
notify('다운로드 요청 실패', 'danger');
|
.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 %}
|
{% block content %}
|
||||||
|
|
||||||
<form id="download">
|
<form id="download">
|
||||||
{{ macros.setting_input_text('url', 'URL', placeholder='http:// 주소', desc='유튜브, 네이버TV 등 동영상 주소') }}
|
{{ 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_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_select('preset', '동영상 포맷 프리셋', arg['preset_list'], col='3') }}
|
||||||
{{ macros.setting_input_text('format', '동영상 포맷', desc=['포맷 지정은 https://github.com/ytdl-org/youtube-dl/#format-selection 참고', '빈칸으로 두면 최고 화질로 다운로드합니다.']) }}
|
{{ 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로 후처리합니다.') }}
|
{{ my_setting_select('postprocessor', '후처리', arg['postprocessor_list'], col='3', desc='다운로드 후 FFmpeg로 후처리합니다.') }}
|
||||||
{{ macros.setting_button([['download_btn', '다운로드']]) }}
|
{{ macros.setting_buttons([['download_btn', '다운로드']]) }}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
"use strict";
|
"use strict";
|
||||||
const package_name = '{{ arg["package_name"] }}';
|
const package_name = '{{ arg["package_name"] }}';
|
||||||
</script>
|
</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 %}
|
{% 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
|
<link
|
||||||
rel="stylesheet"
|
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', '전체 중지') }} {{
|
{{ macros.m_row_start() }} {{ macros.m_button('all_stop_btn', '전체 중지') }} {{
|
||||||
@@ -30,6 +30,6 @@ macros.m_row_end() }}
|
|||||||
'use strict';
|
'use strict';
|
||||||
const package_name = '{{ arg["package_name"] }}';
|
const package_name = '{{ arg["package_name"] }}';
|
||||||
</script>
|
</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 %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -29,6 +29,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const package_name = '{{ arg["package_name"] }}';
|
const package_name = '{{ arg["package_name"] }}';
|
||||||
</script>
|
</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 %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -20,6 +20,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const package_name = '{{ arg["package_name"] }}';
|
const package_name = '{{ arg["package_name"] }}';
|
||||||
</script>
|
</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 %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -13,6 +13,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const package_name = '{{ arg["package_name"] }}';
|
const package_name = '{{ arg["package_name"] }}';
|
||||||
</script>
|
</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 %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user