Files
youtube-dl/plugin.py

503 lines
19 KiB
Python
Raw Normal View History

2020-02-05 20:54:30 +09:00
import os
import traceback
import subprocess
2020-02-05 20:54:30 +09:00
2020-02-14 00:49:47 +09:00
from flask import Blueprint, request, render_template, redirect, jsonify, abort
2020-02-05 20:54:30 +09:00
from flask_login import login_required
2021-05-02 00:36:04 +09:00
from flask_cors import cross_origin
2020-02-05 20:54:30 +09:00
2020-10-10 19:35:59 +09:00
from framework import check_api, socketio
2021-05-02 00:36:04 +09:00
from framework.logger import get_logger
2020-02-05 20:54:30 +09:00
from .logic import Logic
from .logic_normal import LogicNormal
2020-02-05 20:54:30 +09:00
from .model import ModelSetting
2022-04-30 18:57:23 +09:00
package_name = __name__.split(".", maxsplit=1)[0]
2021-05-02 00:36:04 +09:00
logger = get_logger(package_name)
youtube_dl_package = LogicNormal.get_youtube_dl_package(
2022-04-30 18:57:23 +09:00
ModelSetting.get("youtube_dl_package")
if ModelSetting.get("youtube_dl_package")
else Logic.db_default["youtube_dl_package"],
import_pkg=True,
)
2021-02-11 18:08:47 +09:00
2020-02-05 20:54:30 +09:00
#########################################################
2020-03-01 01:34:59 +09:00
# 플러그인 공용
#########################################################
2022-04-30 18:57:23 +09:00
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"),
)
2020-03-01 01:34:59 +09:00
menu = {
2022-04-30 18:57:23 +09:00
"main": [package_name, "youtube-dl"],
"sub": [
["setting", "설정"],
["download", "다운로드"],
["thumbnail", "썸네일 다운로드"],
["sub", "자막 다운로드"],
["list", "목록"],
["log", "로그"],
2020-07-23 23:00:02 +09:00
],
2022-04-30 18:57:23 +09:00
"category": "vod",
2020-03-01 01:34:59 +09:00
}
2020-02-05 20:54:30 +09:00
plugin_info = {
2022-04-30 18:57:23 +09:00
"version": "3.1.0",
"name": "youtube-dl",
"category_name": "vod",
"developer": "joyfuI",
"description": "유튜브, 네이버TV 등 동영상 사이트에서 동영상 다운로드",
"home": "https://github.com/joyfuI/youtube-dl",
"more": "",
2020-02-05 20:54:30 +09:00
}
2021-02-11 18:08:47 +09:00
2020-03-01 01:34:59 +09:00
def plugin_load():
2020-07-23 23:00:02 +09:00
Logic.plugin_load()
2020-03-01 01:34:59 +09:00
2021-02-11 18:08:47 +09:00
2020-03-01 01:34:59 +09:00
def plugin_unload():
2020-07-23 23:00:02 +09:00
Logic.plugin_unload()
2020-02-05 20:54:30 +09:00
2021-02-11 18:08:47 +09:00
2020-02-05 20:54:30 +09:00
#########################################################
# WEB Menu
#########################################################
2022-04-30 18:57:23 +09:00
@blueprint.route("/")
2020-02-05 20:54:30 +09:00
def home():
2022-04-30 18:57:23 +09:00
return redirect(f"/{package_name}/list")
2020-02-05 20:54:30 +09:00
2021-02-11 18:08:47 +09:00
2022-04-30 18:57:23 +09:00
@blueprint.route("/<sub>")
2020-02-05 20:54:30 +09:00
@login_required
def first_menu(sub):
2020-07-23 23:00:02 +09:00
try:
2021-02-11 18:08:47 +09:00
arg = {
2022-04-30 18:57:23 +09:00
"package_name": package_name,
"template_name": f"{package_name}_{sub}",
2021-02-11 18:08:47 +09:00
}
2020-03-01 01:34:59 +09:00
2022-04-30 18:57:23 +09:00
if sub == "setting":
2020-07-23 23:00:02 +09:00
arg.update(ModelSetting.to_dict())
2022-04-30 18:57:23 +09:00
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)
2020-07-23 23:00:02 +09:00
except Exception as e:
2022-04-30 18:57:23 +09:00
logger.error("Exception:%s", e)
2020-07-23 23:00:02 +09:00
logger.error(traceback.format_exc())
2022-04-30 18:57:23 +09:00
return render_template("sample.html", title=f"{package_name} - {sub}")
2020-02-05 20:54:30 +09:00
2021-02-11 18:08:47 +09:00
2020-02-05 20:54:30 +09:00
#########################################################
# For UI
#########################################################
2022-04-30 18:57:23 +09:00
@blueprint.route("/ajax/<sub>", methods=["POST"])
@login_required
2020-02-05 20:54:30 +09:00
def ajax(sub):
2022-04-30 18:57:23 +09:00
logger.debug("AJAX %s %s", package_name, sub)
2020-07-23 23:00:02 +09:00
try:
# 공통 요청
2022-04-30 18:57:23 +09:00
if sub == "setting_save":
2020-07-23 23:00:02 +09:00
ret = ModelSetting.setting_save(request)
2022-04-30 18:57:23 +09:00
if request.form["ffmpeg_path"] == "ffmpeg":
ModelSetting.set("ffmpeg_path", "")
2020-07-23 23:00:02 +09:00
return jsonify(ret)
2020-02-05 20:54:30 +09:00
2020-07-23 23:00:02 +09:00
# UI 요청
2022-04-30 18:57:23 +09:00
elif sub == "ffmpeg_version":
path = request.form["path"]
ret = subprocess.check_output([path, "-version"])
ret = ret.decode().replace("\n", "<br>")
return jsonify(ret)
2022-04-30 18:57:23 +09:00
elif sub == "download":
postprocessor = request.form["postprocessor"]
2020-07-23 23:00:02 +09:00
video_convertor, extract_audio = LogicNormal.get_postprocessor()
preferedformat = None
preferredcodec = None
preferredquality = None
2020-07-23 23:00:02 +09:00
if postprocessor in video_convertor:
preferedformat = postprocessor
2020-07-23 23:00:02 +09:00
elif postprocessor in extract_audio:
preferredcodec = postprocessor
preferredquality = 192
2022-04-30 18:57:23 +09:00
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"),
)
2020-07-23 23:00:02 +09:00
youtube_dl.start()
2022-04-30 18:57:23 +09:00
socketio_emit("add", youtube_dl)
2020-07-23 23:00:02 +09:00
return jsonify([])
2020-02-05 20:54:30 +09:00
2022-04-30 18:57:23 +09:00
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()
2022-04-30 18:57:23 +09:00
socketio_emit("add", youtube_dl)
return jsonify([])
2022-04-30 18:57:23 +09:00
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()
2022-04-30 18:57:23 +09:00
socketio_emit("add", youtube_dl)
return jsonify([])
2022-04-30 18:57:23 +09:00
elif sub == "list":
2020-07-23 23:00:02 +09:00
ret = []
for i in LogicNormal.youtube_dl_list:
data = LogicNormal.get_data(i)
if data is not None:
ret.append(data)
return jsonify(ret)
2020-02-05 20:54:30 +09:00
2022-04-30 18:57:23 +09:00
elif sub == "all_stop":
for i in LogicNormal.youtube_dl_list:
i.stop()
return jsonify([])
2022-04-30 18:57:23 +09:00
elif sub == "stop":
index = int(request.form["index"])
2020-07-23 23:00:02 +09:00
LogicNormal.youtube_dl_list[index].stop()
return jsonify([])
except Exception as e:
2022-04-30 18:57:23 +09:00
logger.error("Exception:%s", e)
2020-07-23 23:00:02 +09:00
logger.error(traceback.format_exc())
2020-02-05 20:54:30 +09:00
2021-02-11 18:08:47 +09:00
2020-02-05 20:54:30 +09:00
#########################################################
# API
#########################################################
2020-02-14 00:49:47 +09:00
# API 명세는 https://github.com/joyfuI/youtube-dl#api
2022-04-30 18:57:23 +09:00
@blueprint.route("/api/<sub>", methods=["GET", "POST"])
2021-05-02 00:36:04 +09:00
@cross_origin()
2020-03-04 11:51:09 +09:00
@check_api
2020-02-05 20:54:30 +09:00
def api(sub):
2022-04-30 18:57:23 +09:00
plugin = request.values.get("plugin")
logger.debug("API %s %s: %s", package_name, sub, plugin)
2020-07-23 23:00:02 +09:00
if not plugin: # 요청한 플러그인명이 빈문자열이거나 None면
abort(403) # 403 에러(거부)
try:
# 동영상 정보를 반환하는 API
2022-04-30 18:57:23 +09:00
if sub == "info_dict":
url = request.values.get("url")
ret = {"errorCode": 0, "info_dict": None}
2020-07-23 23:00:02 +09:00
if None in (url,):
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
2022-04-30 18:57:23 +09:00
if not url.startswith("http"):
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 2) # 잘못된 동영상 주소
2022-04-30 18:57:23 +09:00
info_dict = LogicNormal.get_info_dict(url, ModelSetting.get("proxy"))
2020-07-23 23:00:02 +09:00
if info_dict is None:
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 10) # 실패
2022-04-30 18:57:23 +09:00
ret["info_dict"] = info_dict
2020-07-23 23:00:02 +09:00
return jsonify(ret)
2020-02-14 00:49:47 +09:00
# 비디오 다운로드 준비를 요청하는 API
2022-04-30 18:57:23 +09:00
elif sub == "download":
key = request.values.get("key")
url = request.values.get("url")
filename = request.values.get(
2022-04-30 18:57:23 +09:00
"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}
2020-07-23 23:00:02 +09:00
if None in (key, url):
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
2022-04-30 18:57:23 +09:00
if not url.startswith("http"):
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 2) # 잘못된 동영상 주소
2022-04-30 18:57:23 +09:00
if preferredcodec not in (
None,
"best",
"mp3",
"aac",
"flac",
"m4a",
"opus",
"vorbis",
"wav",
):
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 5) # 허용되지 않은 값이 있음
if not filename:
2020-08-08 15:33:14 +09:00
filename = LogicNormal.get_default_filename()
2022-04-30 18:57:23 +09:00
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,
)
2020-11-08 22:58:35 +09:00
if youtube_dl is None:
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 10) # 실패
2022-04-30 18:57:23 +09:00
ret["index"] = youtube_dl.index
2020-07-23 23:00:02 +09:00
if start:
youtube_dl.start()
2022-04-30 18:57:23 +09:00
socketio_emit("add", youtube_dl)
2020-07-23 23:00:02 +09:00
return jsonify(ret)
2020-02-14 00:49:47 +09:00
# 썸네일 다운로드 준비를 요청하는 API
2022-04-30 18:57:23 +09:00
elif sub == "thumbnail":
key = request.values.get("key")
url = request.values.get("url")
filename = request.values.get(
2022-04-30 18:57:23 +09:00
"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) # 필수 요청 변수가 없음
2022-04-30 18:57:23 +09:00
if not url.startswith("http"):
return LogicNormal.abort(ret, 2) # 잘못된 동영상 주소
if not filename:
filename = LogicNormal.get_default_filename()
2022-04-30 18:57:23 +09:00
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) # 실패
2022-04-30 18:57:23 +09:00
ret["index"] = youtube_dl.index
if start:
youtube_dl.start()
2022-04-30 18:57:23 +09:00
socketio_emit("add", youtube_dl)
return jsonify(ret)
# 자막 다운로드 준비를 요청하는 API
2022-04-30 18:57:23 +09:00
elif sub == "sub":
key = request.values.get("key")
url = request.values.get("url")
filename = request.values.get(
2022-04-30 18:57:23 +09:00
"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) # 필수 요청 변수가 없음
2022-04-30 18:57:23 +09:00
if not url.startswith("http"):
return LogicNormal.abort(ret, 2) # 잘못된 동영상 주소
if not filename:
filename = LogicNormal.get_default_filename()
2022-04-30 18:57:23 +09:00
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) # 실패
2022-04-30 18:57:23 +09:00
ret["index"] = youtube_dl.index
if start:
youtube_dl.start()
2022-04-30 18:57:23 +09:00
socketio_emit("add", youtube_dl)
return jsonify(ret)
2020-07-23 23:00:02 +09:00
# 다운로드 시작을 요청하는 API
2022-04-30 18:57:23 +09:00
elif sub == "start":
index = request.values.get("index")
key = request.values.get("key")
ret = {"errorCode": 0, "status": None}
2020-07-23 23:00:02 +09:00
if None in (index, key):
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
2020-07-23 23:00:02 +09:00
index = int(index)
2022-04-30 18:57:23 +09:00
if not 0 <= index < len(LogicNormal.youtube_dl_list):
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 3) # 인덱스 범위를 벗어남
2020-07-23 23:00:02 +09:00
youtube_dl = LogicNormal.youtube_dl_list[index]
if youtube_dl.key != key:
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 4) # 키가 일치하지 않음
2022-04-30 18:57:23 +09:00
ret["status"] = youtube_dl.status.name
2020-07-23 23:00:02 +09:00
if not youtube_dl.start():
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 10) # 실패
2020-07-23 23:00:02 +09:00
return jsonify(ret)
2020-02-14 00:49:47 +09:00
2020-07-23 23:00:02 +09:00
# 다운로드 중지를 요청하는 API
2022-04-30 18:57:23 +09:00
elif sub == "stop":
index = request.values.get("index")
key = request.values.get("key")
ret = {"errorCode": 0, "status": None}
2020-07-23 23:00:02 +09:00
if None in (index, key):
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
2020-07-23 23:00:02 +09:00
index = int(index)
2022-04-30 18:57:23 +09:00
if not 0 <= index < len(LogicNormal.youtube_dl_list):
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 3) # 인덱스 범위를 벗어남
2020-07-23 23:00:02 +09:00
youtube_dl = LogicNormal.youtube_dl_list[index]
if youtube_dl.key != key:
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 4) # 키가 일치하지 않음
2022-04-30 18:57:23 +09:00
ret["status"] = youtube_dl.status.name
2020-07-23 23:00:02 +09:00
if not youtube_dl.stop():
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 10) # 실패
2020-07-23 23:00:02 +09:00
return jsonify(ret)
2020-02-14 00:49:47 +09:00
2020-07-23 23:00:02 +09:00
# 현재 상태를 반환하는 API
2022-04-30 18:57:23 +09:00
elif sub == "status":
index = request.values.get("index")
key = request.values.get("key")
2020-07-23 23:00:02 +09:00
ret = {
2022-04-30 18:57:23 +09:00
"errorCode": 0,
"status": None,
"type": None,
"start_time": None,
"end_time": None,
"temp_path": None,
"save_path": None,
2020-07-23 23:00:02 +09:00
}
if None in (index, key):
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 1) # 필수 요청 변수가 없음
2020-07-23 23:00:02 +09:00
index = int(index)
2022-04-30 18:57:23 +09:00
if not 0 <= index < len(LogicNormal.youtube_dl_list):
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 3) # 인덱스 범위를 벗어남
2020-07-23 23:00:02 +09:00
youtube_dl = LogicNormal.youtube_dl_list[index]
if youtube_dl.key != key:
2021-02-11 18:08:47 +09:00
return LogicNormal.abort(ret, 4) # 키가 일치하지 않음
2022-04-30 18:57:23 +09:00
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
2020-07-23 23:00:02 +09:00
return jsonify(ret)
except Exception as e:
2022-04-30 18:57:23 +09:00
logger.error("Exception:%s", e)
2020-07-23 23:00:02 +09:00
logger.error(traceback.format_exc())
abort(500) # 500 에러(서버 오류)
2021-02-11 18:08:47 +09:00
abort(404) # 404 에러(페이지 없음)
2020-03-01 01:34:59 +09:00
#########################################################
# socketio
#########################################################
2020-03-19 00:32:24 +09:00
def socketio_emit(cmd, data):
2022-04-30 18:57:23 +09:00
socketio.emit(
cmd, LogicNormal.get_data(data), namespace=f"/{package_name}", broadcast=True
)