diff --git a/README.md b/README.md
index 10a2af0..100f8c6 100644
--- a/README.md
+++ b/README.md
@@ -245,6 +245,10 @@ API를 제공합니다. 다른 플러그인에서 동영상 정보나 다운로
## Changelog
+v4.0.0
+
+- 최신 플러그인 구조로 변경
+
v3.1.1
- 디폴트 youtube_dl 패키지를 yt-dlp로 변경
diff --git a/__init__.py b/__init__.py
index e962124..2149051 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1 +1,7 @@
-from .plugin import blueprint, menu, plugin_info, plugin_load, plugin_unload
+from .plugin import Plugin
+
+blueprint = Plugin.blueprint
+menu = Plugin.menu
+plugin_load = Plugin.logic.plugin_load
+plugin_unload = Plugin.logic.plugin_unload
+plugin_info = Plugin.plugin_info
diff --git a/abort.py b/abort.py
new file mode 100644
index 0000000..623250e
--- /dev/null
+++ b/abort.py
@@ -0,0 +1,8 @@
+from flask import jsonify
+
+
+class LogicAbort:
+ @staticmethod
+ def abort(base, code):
+ base["errorCode"] = code
+ return jsonify(base)
diff --git a/info.json b/info.json
index db8a0f3..374edde 100644
--- a/info.json
+++ b/info.json
@@ -1,10 +1,9 @@
{
- "version": "3.1.1",
+ "version": "4.0.0",
"name": "youtube-dl",
"category_name": "vod",
"developer": "joyfuI",
"description": "유튜브, 네이버TV 등 동영상 사이트에서 동영상 다운로드",
"home": "https://github.com/joyfuI/youtube-dl",
- "more": "",
- "category": "vod"
+ "more": ""
}
\ No newline at end of file
diff --git a/logic.py b/logic.py
deleted file mode 100644
index dcc3235..0000000
--- a/logic.py
+++ /dev/null
@@ -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": "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": "",
- }
-
- @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 error:
- logger.error("Exception:%s", error)
- 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 error:
- logger.error("Exception:%s", error)
- logger.error(traceback.format_exc())
-
- @staticmethod
- def plugin_unload():
- try:
- logger.debug("%s plugin_unload", package_name)
- except Exception as error:
- logger.error("Exception:%s", error)
- 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 error:
- logger.error("Exception:%s", error)
- logger.error(traceback.format_exc())
diff --git a/logic_normal.py b/main.py
similarity index 58%
rename from logic_normal.py
rename to main.py
index 3648921..8b41c54 100644
--- a/logic_normal.py
+++ b/main.py
@@ -1,28 +1,230 @@
+import os
+import sys
+import platform
import traceback
+import subprocess
+import sqlite3
from datetime import datetime
-from flask import jsonify
+from flask import render_template, jsonify
-from framework.logger import get_logger
+from framework import db, path_app_root, path_data, socketio
+from framework.common.plugin import LogicModuleBase, default_route_socketio
+from .plugin import Plugin
from .my_youtube_dl import MyYoutubeDL, Status
-package_name = __name__.split(".", maxsplit=1)[0]
-logger = get_logger(package_name)
+logger = Plugin.logger
+package_name = Plugin.package_name
+ModelSetting = Plugin.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", "
")
+ 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")
+ if req.form["ffmpeg_path"] != "ffmpeg"
+ else None,
+ )
+ youtube_dl.start()
+ LogicMain.socketio_emit("add", youtube_dl)
+
+ 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")
+ if req.form["ffmpeg_path"] != "ffmpeg"
+ else None,
+ )
+ youtube_dl.start()
+ LogicMain.socketio_emit("add", youtube_dl)
+
+ 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")
+ if req.form["ffmpeg_path"] != "ffmpeg"
+ else None,
+ )
+ youtube_dl.start()
+ LogicMain.socketio_emit("add", youtube_dl)
+
+ elif sub == "list":
+ ret["data"] = []
+ for i in LogicMain.youtube_dl_list:
+ data = LogicMain.get_data(i)
+ if data is not None:
+ ret["data"].append(data)
+
+ elif sub == "all_stop":
+ for i in LogicMain.youtube_dl_list:
+ i.stop()
+
+ elif sub == "stop":
+ index = int(req.form["index"])
+ LogicMain.youtube_dl_list[index].stop()
+
+ return jsonify(ret)
+ except Exception as error:
+ logger.error("Exception:%s", error)
+ logger.error(traceback.format_exc())
+ return jsonify({"ret": "danger", "msg": str(error)})
+
+ def migration(self):
+ try:
+ db_version = ModelSetting.get_int("db_version")
+ connect = sqlite3.connect(
+ os.path.join(path_data, "db", f"{package_name}.db")
+ )
+
+ if db_version < 2:
+ logger.debug("youtube-dlc uninstall")
+ logger.debug(
+ subprocess.check_output(
+ [sys.executable, "-m", "pip", "uninstall", "-y", "youtube-dlc"],
+ universal_newlines=True,
+ )
+ )
+
+ connect.commit()
+ connect.close()
+ ModelSetting.set("db_version", LogicMain.db_default["db_version"])
+ db.session.flush()
+ except Exception as error:
+ logger.error("Exception:%s", error)
+ logger.error(traceback.format_exc())
+
youtube_dl_list = []
- @staticmethod
- def get_youtube_dl_package(index=None, import_pkg=False):
- packages = ["youtube-dl", "yt-dlp"]
- import_name = ["youtube_dl", "yt_dlp"]
- if import_pkg:
- return import_name if index is None else import_name[int(index)]
- else:
- return packages if index is None else packages[int(index)]
-
@staticmethod
def get_youtube_dl_version():
try:
@@ -79,7 +281,7 @@ class LogicNormal(object):
def get_postprocessor():
video_convertor = []
extract_audio = []
- for i in LogicNormal.get_postprocessor_list():
+ for i in LogicMain.get_postprocessor_list():
if i[2] == "비디오 변환":
video_convertor.append(i[0])
elif i[2] == "오디오 추출":
@@ -136,7 +338,7 @@ class LogicNormal(object):
plugin, "video", url, filename, temp_path, save_path, opts, dateafter
)
youtube_dl.key = kwagrs.get("key")
- LogicNormal.youtube_dl_list.append(youtube_dl) # 리스트 추가
+ LogicMain.youtube_dl_list.append(youtube_dl) # 리스트 추가
return youtube_dl
except Exception as error:
logger.error("Exception:%s", error)
@@ -187,7 +389,7 @@ class LogicNormal(object):
dateafter,
)
youtube_dl.key = kwagrs.get("key")
- LogicNormal.youtube_dl_list.append(youtube_dl) # 리스트 추가
+ LogicMain.youtube_dl_list.append(youtube_dl) # 리스트 추가
return youtube_dl
except Exception as error:
logger.error("Exception:%s", error)
@@ -235,7 +437,7 @@ class LogicNormal(object):
plugin, "subtitle", url, filename, temp_path, save_path, opts, dateafter
)
youtube_dl.key = kwagrs.get("key")
- LogicNormal.youtube_dl_list.append(youtube_dl) # 리스트 추가
+ LogicMain.youtube_dl_list.append(youtube_dl) # 리스트 추가
return youtube_dl
except Exception as error:
logger.error("Exception:%s", error)
@@ -284,9 +486,7 @@ class LogicNormal(object):
else ""
)
data["speed_str"] = (
- LogicNormal.human_readable_size(
- youtube_dl.progress_hooks["speed"], "/s"
- )
+ LogicMain.human_readable_size(youtube_dl.progress_hooks["speed"], "/s")
if youtube_dl.progress_hooks["speed"] is not None
else ""
)
@@ -303,10 +503,10 @@ class LogicNormal(object):
youtube_dl.progress_hooks["downloaded_bytes"],
youtube_dl.progress_hooks["total_bytes"],
): # 둘 다 값이 있으면
- data["downloaded_bytes_str"] = LogicNormal.human_readable_size(
+ data["downloaded_bytes_str"] = LogicMain.human_readable_size(
youtube_dl.progress_hooks["downloaded_bytes"]
)
- data["total_bytes_str"] = LogicNormal.human_readable_size(
+ data["total_bytes_str"] = LogicMain.human_readable_size(
youtube_dl.progress_hooks["total_bytes"]
)
data[
@@ -335,6 +535,7 @@ class LogicNormal(object):
return f"{size:.1f} YB{suffix}"
@staticmethod
- def abort(base, code):
- base["errorCode"] = code
- return jsonify(base)
+ def socketio_emit(cmd, data):
+ socketio.emit(
+ cmd, LogicMain.get_data(data), namespace=f"/{package_name}", broadcast=True
+ )
diff --git a/model.py b/model.py
deleted file mode 100644
index 7f9a8f1..0000000
--- a/model.py
+++ /dev/null
@@ -1,119 +0,0 @@
-import os
-import traceback
-
-from framework import app, db, path_data
-from framework.logger import get_logger
-from framework.util import Util
-
-package_name = __name__.split(".", maxsplit=1)[0]
-logger = get_logger(package_name)
-app.config["SQLALCHEMY_BINDS"][package_name] = "sqlite:///%s" % (
- os.path.join(path_data, "db", f"{package_name}.db")
-)
-
-
-class ModelSetting(db.Model):
- __tablename__ = f"{package_name}_setting"
- __table_args__ = {"mysql_collate": "utf8_general_ci"}
- __bind_key__ = package_name
-
- id = db.Column(db.Integer, primary_key=True)
- key = db.Column(db.String(100), unique=True, nullable=False)
- value = db.Column(db.String, nullable=False)
-
- def __init__(self, key, value):
- self.key = key
- self.value = value
-
- def __repr__(self):
- return repr(self.as_dict())
-
- def as_dict(self):
- return {x.name: getattr(self, x.name) for x in self.__table__.columns}
-
- @staticmethod
- def get(key):
- try:
- return (
- db.session.query(ModelSetting).filter_by(key=key).first().value.strip()
- )
- except Exception as error:
- logger.error("Exception:%s %s", error, key)
- logger.error(traceback.format_exc())
-
- @staticmethod
- def get_int(key):
- try:
- return int(ModelSetting.get(key))
- except Exception as error:
- logger.error("Exception:%s %s", error, key)
- logger.error(traceback.format_exc())
-
- @staticmethod
- def get_bool(key):
- try:
- return ModelSetting.get(key) == "True"
- except Exception as error:
- logger.error("Exception:%s %s", error, 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 error:
- logger.error("Exception:%s", error)
- 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 error:
- logger.error("Exception:%s", error)
- 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 error:
- logger.error("Exception:%s", error)
- 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 error:
- logger.error("Exception:%s %s", error, key)
- logger.error(traceback.format_exc())
diff --git a/my_youtube_dl.py b/my_youtube_dl.py
index 48f9684..94196b1 100644
--- a/my_youtube_dl.py
+++ b/my_youtube_dl.py
@@ -8,11 +8,18 @@ from datetime import datetime
from threading import Thread
from enum import Enum
-from framework.logger import get_logger
import framework.common.celery as celery_shutil
-package_name = __name__.split(".", maxsplit=1)[0]
-logger = get_logger(package_name)
+from .plugin import Plugin
+
+logger = Plugin.logger
+ModelSetting = Plugin.ModelSetting
+
+youtube_dl_package = Plugin.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):
@@ -29,7 +36,7 @@ class Status(Enum):
return str_list[self.value]
-class MyYoutubeDL(object):
+class MyYoutubeDL:
DEFAULT_FILENAME = "%(title)s-%(id)s.%(ext)s"
_index = 0
@@ -47,8 +54,6 @@ class MyYoutubeDL(object):
datebefore=None,
):
# from youtube_dl.utils import DateRange
- from .plugin import youtube_dl_package
-
DateRange = __import__(
f"{youtube_dl_package}.utils", fromlist=["DateRange"]
).DateRange
@@ -106,8 +111,6 @@ class MyYoutubeDL(object):
def run(self):
# import youtube_dl
- from .plugin import youtube_dl_package
-
youtube_dl = __import__(youtube_dl_package)
try:
@@ -164,8 +167,6 @@ class MyYoutubeDL(object):
@staticmethod
def get_version():
# from youtube_dl.version import __version__
- from .plugin import youtube_dl_package
-
__version__ = __import__(
f"{youtube_dl_package}.version", fromlist=["__version__"]
).__version__
@@ -175,8 +176,6 @@ class MyYoutubeDL(object):
@staticmethod
def get_info_dict(url, proxy=None, cookiefile=None):
# import youtube_dl
- from .plugin import youtube_dl_package
-
youtube_dl = __import__(youtube_dl_package)
try:
@@ -220,13 +219,13 @@ class MyYoutubeDL(object):
@status.setter
def status(self, value):
- from .plugin import socketio_emit
+ from .main import LogicMain
self._status = value
- socketio_emit("status", self)
+ LogicMain.socketio_emit("status", self)
-class MyLogger(object):
+class MyLogger:
def debug(self, msg):
if msg.find(" ETA ") != -1:
# 과도한 로그 방지
diff --git a/plugin.py b/plugin.py
index 777d18a..bbf3d8a 100644
--- a/plugin.py
+++ b/plugin.py
@@ -1,274 +1,117 @@
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 flask import Blueprint, request, jsonify, abort
-from framework import check_api, socketio
+from framework import app, path_data
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,
+from framework.util import Util
+from framework.common.plugin import (
+ get_model_setting,
+ Logic,
+ default_route_single_module,
)
-#########################################################
-# 플러그인 공용
-#########################################################
-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",
-}
+class Plugin:
+ package_name = __name__.split(".", maxsplit=1)[0]
+ logger = get_logger(package_name)
+ 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"),
+ )
-plugin_info = {
- "version": "3.1.1",
- "name": "youtube-dl",
- "category_name": "vod",
- "developer": "joyfuI",
- "description": "유튜브, 네이버TV 등 동영상 사이트에서 동영상 다운로드",
- "home": "https://github.com/joyfuI/youtube-dl",
- "more": "",
-}
+ # 메뉴 정의
+ menu = {
+ "main": [package_name, "youtube-dl"],
+ "sub": [
+ ["setting", "설정"],
+ ["download", "다운로드"],
+ ["thumbnail", "썸네일 다운로드"],
+ ["sub", "자막 다운로드"],
+ ["list", "목록"],
+ ["log", "로그"],
+ ],
+ "category": "vod",
+ }
+
+ plugin_info = {
+ "version": "4.0.0",
+ "name": package_name,
+ "category_name": "vod",
+ "developer": "joyfuI",
+ "description": "유튜브, 네이버TV 등 동영상 사이트에서 동영상 다운로드",
+ "home": f"https://github.com/joyfuI/{package_name}",
+ "more": "",
+ }
+
+ ModelSetting = get_model_setting(package_name, logger)
+ logic = None
+ module_list = None
+ home_module = "list" # 기본모듈
+
+ youtube_dl_packages = ["youtube-dl", "yt-dlp"]
-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("/")
-@login_required
-def first_menu(sub):
+def initialize():
try:
- arg = {
- "package_name": package_name,
- "template_name": f"{package_name}_{sub}",
- "package_version": plugin_info["version"],
- }
+ app.config["SQLALCHEMY_BINDS"][
+ Plugin.package_name
+ ] = f"sqlite:///{os.path.join(path_data, 'db', f'{Plugin.package_name}.db')}"
+ Util.save_from_dict_to_json(
+ Plugin.plugin_info, os.path.join(os.path.dirname(__file__), "info.json")
+ )
- 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)
+ # 로드할 모듈 정의
+ from .main import LogicMain
- 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)
+ Plugin.module_list = [LogicMain(Plugin)]
- 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)
+ Plugin.logic = Logic(Plugin)
+ default_route_single_module(Plugin)
except Exception as error:
- logger.error("Exception:%s", error)
- logger.error(traceback.format_exc())
- return render_template("sample.html", title=f"{package_name} - {sub}")
+ Plugin.logger.error("Exception:%s", error)
+ Plugin.logger.error(traceback.format_exc())
-#########################################################
-# For UI
-#########################################################
-@blueprint.route("/ajax/", 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", "
")
- 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 error:
- logger.error("Exception:%s", error)
- logger.error(traceback.format_exc())
-
-
-#########################################################
-# API
-#########################################################
# API 명세는 https://github.com/joyfuI/youtube-dl#api
-@blueprint.route("/api/", methods=["GET", "POST"])
-@cross_origin()
-@check_api
+@Plugin.blueprint.route("/api/", methods=["GET", "POST"])
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 에러(거부)
+ from .main import LogicMain
+ from .abort import LogicAbort
+
try:
+ Plugin.logger.debug("API: %s, %s", sub, request.values)
+ plugin = request.values.get("plugin")
+ if not plugin: # 요청한 플러그인명이 빈문자열이거나 None면
+ abort(403) # 403 에러(거부)
+
# 동영상 정보를 반환하는 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) # 필수 요청 변수가 없음
+ return LogicAbort.abort(ret, 1) # 필수 요청 변수가 없음
if not url.startswith("http"):
- return LogicNormal.abort(ret, 2) # 잘못된 동영상 주소
- info_dict = LogicNormal.get_info_dict(url, ModelSetting.get("proxy"))
+ return LogicAbort.abort(ret, 2) # 잘못된 동영상 주소
+ info_dict = LogicMain.get_info_dict(url, Plugin.ModelSetting.get("proxy"))
if info_dict is None:
- return LogicNormal.abort(ret, 10) # 실패
+ return LogicAbort.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")
+ "filename", Plugin.ModelSetting.get("default_filename")
+ )
+ save_path = request.values.get(
+ "save_path", Plugin.ModelSetting.get("save_path")
)
- 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)
@@ -280,9 +123,9 @@ def api(sub):
cookiefile = request.values.get("cookiefile", None)
ret = {"errorCode": 0, "index": None}
if None in (key, url):
- return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
+ return LogicAbort.abort(ret, 1) # 필수 요청 변수가 없음
if not url.startswith("http"):
- return LogicNormal.abort(ret, 2) # 잘못된 동영상 주소
+ return LogicAbort.abort(ret, 2) # 잘못된 동영상 주소
if preferredcodec not in (
None,
"best",
@@ -294,14 +137,14 @@ def api(sub):
"vorbis",
"wav",
):
- return LogicNormal.abort(ret, 5) # 허용되지 않은 값이 있음
+ return LogicAbort.abort(ret, 5) # 허용되지 않은 값이 있음
if not filename:
- filename = LogicNormal.get_default_filename()
- youtube_dl = LogicNormal.download(
+ filename = LogicMain.get_default_filename()
+ youtube_dl = LogicMain.download(
plugin=plugin,
url=url,
filename=filename,
- temp_path=ModelSetting.get("temp_path"),
+ temp_path=Plugin.ModelSetting.get("temp_path"),
save_path=save_path,
format=format_code,
preferedformat=preferedformat,
@@ -310,27 +153,28 @@ def api(sub):
dateafter=dateafter,
playlist=playlist,
archive=archive,
- proxy=ModelSetting.get("proxy"),
- ffmpeg_path=ModelSetting.get("ffmpeg_path"),
+ proxy=Plugin.ModelSetting.get("proxy"),
+ ffmpeg_path=Plugin.ModelSetting.get("ffmpeg_path"),
key=key,
cookiefile=cookiefile,
)
if youtube_dl is None:
- return LogicNormal.abort(ret, 10) # 실패
+ return LogicAbort.abort(ret, 10) # 실패
ret["index"] = youtube_dl.index
if start:
youtube_dl.start()
- socketio_emit("add", youtube_dl)
- return jsonify(ret)
+ LogicMain.socketio_emit("add", youtube_dl)
# 썸네일 다운로드 준비를 요청하는 API
elif sub == "thumbnail":
key = request.values.get("key")
url = request.values.get("url")
filename = request.values.get(
- "filename", ModelSetting.get("default_filename")
+ "filename", Plugin.ModelSetting.get("default_filename")
+ )
+ save_path = request.values.get(
+ "save_path", Plugin.ModelSetting.get("save_path")
)
- 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)
@@ -339,42 +183,43 @@ def api(sub):
cookiefile = request.values.get("cookiefile", None)
ret = {"errorCode": 0, "index": None}
if None in (key, url):
- return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
+ return LogicAbort.abort(ret, 1) # 필수 요청 변수가 없음
if not url.startswith("http"):
- return LogicNormal.abort(ret, 2) # 잘못된 동영상 주소
+ return LogicAbort.abort(ret, 2) # 잘못된 동영상 주소
if not filename:
- filename = LogicNormal.get_default_filename()
- youtube_dl = LogicNormal.thumbnail(
+ filename = LogicMain.get_default_filename()
+ youtube_dl = LogicMain.thumbnail(
plugin=plugin,
url=url,
filename=filename,
- temp_path=ModelSetting.get("temp_path"),
+ temp_path=Plugin.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"),
+ proxy=Plugin.ModelSetting.get("proxy"),
+ ffmpeg_path=Plugin.ModelSetting.get("ffmpeg_path"),
key=key,
cookiefile=cookiefile,
)
if youtube_dl is None:
- return LogicNormal.abort(ret, 10) # 실패
+ return LogicAbort.abort(ret, 10) # 실패
ret["index"] = youtube_dl.index
if start:
youtube_dl.start()
- socketio_emit("add", youtube_dl)
- return jsonify(ret)
+ LogicMain.socketio_emit("add", youtube_dl)
# 자막 다운로드 준비를 요청하는 API
elif sub == "sub":
key = request.values.get("key")
url = request.values.get("url")
filename = request.values.get(
- "filename", ModelSetting.get("default_filename")
+ "filename", Plugin.ModelSetting.get("default_filename")
+ )
+ save_path = request.values.get(
+ "save_path", Plugin.ModelSetting.get("save_path")
)
- 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)
@@ -385,16 +230,16 @@ def api(sub):
cookiefile = request.values.get("cookiefile", None)
ret = {"errorCode": 0, "index": None}
if None in (key, url):
- return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
+ return LogicAbort.abort(ret, 1) # 필수 요청 변수가 없음
if not url.startswith("http"):
- return LogicNormal.abort(ret, 2) # 잘못된 동영상 주소
+ return LogicAbort.abort(ret, 2) # 잘못된 동영상 주소
if not filename:
- filename = LogicNormal.get_default_filename()
- youtube_dl = LogicNormal.sub(
+ filename = LogicMain.get_default_filename()
+ youtube_dl = LogicMain.sub(
plugin=plugin,
url=url,
filename=filename,
- temp_path=ModelSetting.get("temp_path"),
+ temp_path=Plugin.ModelSetting.get("temp_path"),
save_path=save_path,
all_subs=all_subs,
sub_lang=sub_lang,
@@ -402,18 +247,17 @@ def api(sub):
dateafter=dateafter,
playlist=playlist,
archive=archive,
- proxy=ModelSetting.get("proxy"),
- ffmpeg_path=ModelSetting.get("ffmpeg_path"),
+ proxy=Plugin.ModelSetting.get("proxy"),
+ ffmpeg_path=Plugin.ModelSetting.get("ffmpeg_path"),
key=key,
cookiefile=cookiefile,
)
if youtube_dl is None:
- return LogicNormal.abort(ret, 10) # 실패
+ return LogicAbort.abort(ret, 10) # 실패
ret["index"] = youtube_dl.index
if start:
youtube_dl.start()
- socketio_emit("add", youtube_dl)
- return jsonify(ret)
+ LogicMain.socketio_emit("add", youtube_dl)
# 다운로드 시작을 요청하는 API
elif sub == "start":
@@ -421,17 +265,16 @@ def api(sub):
key = request.values.get("key")
ret = {"errorCode": 0, "status": None}
if None in (index, key):
- return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
+ return LogicAbort.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 not 0 <= index < len(LogicMain.youtube_dl_list):
+ return LogicAbort.abort(ret, 3) # 인덱스 범위를 벗어남
+ youtube_dl = LogicMain.youtube_dl_list[index]
if youtube_dl.key != key:
- return LogicNormal.abort(ret, 4) # 키가 일치하지 않음
+ return LogicAbort.abort(ret, 4) # 키가 일치하지 않음
ret["status"] = youtube_dl.status.name
if not youtube_dl.start():
- return LogicNormal.abort(ret, 10) # 실패
- return jsonify(ret)
+ return LogicAbort.abort(ret, 10) # 실패
# 다운로드 중지를 요청하는 API
elif sub == "stop":
@@ -439,17 +282,16 @@ def api(sub):
key = request.values.get("key")
ret = {"errorCode": 0, "status": None}
if None in (index, key):
- return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
+ return LogicAbort.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 not 0 <= index < len(LogicMain.youtube_dl_list):
+ return LogicAbort.abort(ret, 3) # 인덱스 범위를 벗어남
+ youtube_dl = LogicMain.youtube_dl_list[index]
if youtube_dl.key != key:
- return LogicNormal.abort(ret, 4) # 키가 일치하지 않음
+ return LogicAbort.abort(ret, 4) # 키가 일치하지 않음
ret["status"] = youtube_dl.status.name
if not youtube_dl.stop():
- return LogicNormal.abort(ret, 10) # 실패
- return jsonify(ret)
+ return LogicAbort.abort(ret, 10) # 실패
# 현재 상태를 반환하는 API
elif sub == "status":
@@ -465,13 +307,13 @@ def api(sub):
"save_path": None,
}
if None in (index, key):
- return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
+ return LogicAbort.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 not 0 <= index < len(LogicMain.youtube_dl_list):
+ return LogicAbort.abort(ret, 3) # 인덱스 범위를 벗어남
+ youtube_dl = LogicMain.youtube_dl_list[index]
if youtube_dl.key != key:
- return LogicNormal.abort(ret, 4) # 키가 일치하지 않음
+ return LogicAbort.abort(ret, 4) # 키가 일치하지 않음
ret["status"] = youtube_dl.status.name
ret["type"] = youtube_dl.type
ret["start_time"] = (
@@ -486,18 +328,12 @@ def api(sub):
)
ret["temp_path"] = youtube_dl.temp_path
ret["save_path"] = youtube_dl.save_path
- return jsonify(ret)
+
+ return jsonify(ret)
except Exception as error:
- logger.error("Exception:%s", error)
- logger.error(traceback.format_exc())
- abort(500) # 500 에러(서버 오류)
- abort(404) # 404 에러(페이지 없음)
+ Plugin.logger.error("Exception:%s", error)
+ Plugin.logger.error(traceback.format_exc())
-#########################################################
-# socketio
-#########################################################
-def socketio_emit(cmd, data):
- socketio.emit(
- cmd, LogicNormal.get_data(data), namespace=f"/{package_name}", broadcast=True
- )
+logger = Plugin.logger
+initialize()