Merge pull request #176 from arabcoders/dev
This commit is contained in:
commit
bcf5593523
8 changed files with 141 additions and 60 deletions
73
.github/workflows/main.yml
vendored
73
.github/workflows/main.yml
vendored
|
|
@ -92,7 +92,7 @@ jobs:
|
||||||
cache-to: type=gha, scope=${{ github.workflow }}
|
cache-to: type=gha, scope=${{ github.workflow }}
|
||||||
|
|
||||||
- name: Version tag
|
- name: Version tag
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name == 'disabled_now'
|
||||||
uses: arabcoders/action-python-autotagger@master
|
uses: arabcoders/action-python-autotagger@master
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
@ -102,7 +102,7 @@ jobs:
|
||||||
|
|
||||||
dockerhub-sync-readme:
|
dockerhub-sync-readme:
|
||||||
needs: push-build
|
needs: push-build
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name == 'push'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Sync README
|
- name: Sync README
|
||||||
|
|
@ -116,3 +116,72 @@ jobs:
|
||||||
with:
|
with:
|
||||||
entrypoint: node
|
entrypoint: node
|
||||||
args: /opt/docker-readme-sync/sync
|
args: /opt/docker-readme-sync/sync
|
||||||
|
|
||||||
|
create_release:
|
||||||
|
needs: push-build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref == 'refs/heads/master' && github.event_name == 'push' && success()
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # so we can git log the full history
|
||||||
|
|
||||||
|
- name: Get commits between old and new
|
||||||
|
id: commits
|
||||||
|
run: |
|
||||||
|
PREVIOUS_SHA="${{ github.event.before }}"
|
||||||
|
CURRENT_SHA="${{ github.event.after }}"
|
||||||
|
|
||||||
|
echo "Previous SHA: $PREVIOUS_SHA"
|
||||||
|
echo "Current SHA: $CURRENT_SHA"
|
||||||
|
|
||||||
|
# If "before" is empty or all zeros, log all commits
|
||||||
|
if [ -z "$PREVIOUS_SHA" ] || [ "$PREVIOUS_SHA" = "0000000000000000000000000000000000000000" ]; then
|
||||||
|
LOG=$(git log --pretty=format:"- %h %s by %an")
|
||||||
|
else
|
||||||
|
LOG=$(git log "$PREVIOUS_SHA".."$CURRENT_SHA" --pretty=format:"- %h %s by %an")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "LOG<<EOF" >> "$GITHUB_ENV"
|
||||||
|
echo "$LOG" >> "$GITHUB_ENV"
|
||||||
|
echo "EOF" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Create local tag matching Docker's raw pattern
|
||||||
|
id: createtag
|
||||||
|
run: |
|
||||||
|
# We'll replicate raw format: branch + base_ref + date + short sha
|
||||||
|
# If base_ref is empty (normal push to master, no PR?), it won't appear.
|
||||||
|
BRANCH_NAME="${{ github.ref_name }}"
|
||||||
|
BASE_REF="${{ github.base_ref }}"
|
||||||
|
CURRENT_SHA="${{ github.event.after }}"
|
||||||
|
DATE=$(date +%Y%m%d)
|
||||||
|
SHORT_SHA=$(echo "$CURRENT_SHA" | cut -c1-7)
|
||||||
|
|
||||||
|
TAG="${BRANCH_NAME}${BASE_REF}-${DATE}-${SHORT_SHA}"
|
||||||
|
|
||||||
|
echo "Using tag: $TAG"
|
||||||
|
|
||||||
|
git config user.name "github-actions"
|
||||||
|
git config user.email "github-actions@github.com"
|
||||||
|
|
||||||
|
# Create a lightweight local tag
|
||||||
|
git tag "$TAG" "$CURRENT_SHA"
|
||||||
|
|
||||||
|
# Push the tag back to GitHub
|
||||||
|
git push origin "$TAG"
|
||||||
|
|
||||||
|
# Expose as an output
|
||||||
|
echo "TAG=$TAG" >> "$GITHUB_OUTPUT"
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@master
|
||||||
|
with:
|
||||||
|
tag_name: ${{ steps.createtag.outputs.TAG }}
|
||||||
|
name: "${{ steps.createtag.outputs.TAG }}"
|
||||||
|
body: ${{ env.LOG }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
generate_release_notes: true
|
||||||
|
append_body: true
|
||||||
|
make_latest: true
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
|
||||||
|
|
@ -95,13 +95,13 @@ class Download:
|
||||||
def _progress_hook(self, data: dict):
|
def _progress_hook(self, data: dict):
|
||||||
dataDict = {k: v for k, v in data.items() if k in self._ytdlp_fields}
|
dataDict = {k: v for k, v in data.items() if k in self._ytdlp_fields}
|
||||||
|
|
||||||
if "finished" == data.get("status", None) and data.get("info_dict", {}).get("filename", False):
|
if "finished" == data.get("status", None) and data.get("info_dict", {}).get("filename", None):
|
||||||
dataDict["filename"] = data["info_dict"]["filename"]
|
dataDict["filename"] = data["info_dict"]["filename"]
|
||||||
|
|
||||||
self.status_queue.put({"id": self.id, **dataDict})
|
self.status_queue.put({"id": self.id, **dataDict})
|
||||||
|
|
||||||
def _postprocessor_hook(self, data: dict):
|
def _postprocessor_hook(self, data: dict):
|
||||||
if data.get("postprocessor") != "MoveFiles" or data.get("status") != "finished":
|
if "MoveFiles" != data.get("postprocessor") or "finished" != data.get("status"):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.debug:
|
if self.debug:
|
||||||
|
|
@ -116,24 +116,22 @@ class Download:
|
||||||
|
|
||||||
def _download(self):
|
def _download(self):
|
||||||
try:
|
try:
|
||||||
params: dict = {
|
params: dict = get_opts(self.preset, mergeConfig(self.default_ytdl_opts, self.ytdl_opts))
|
||||||
"color": "no_color",
|
params.update(
|
||||||
"paths": {
|
{
|
||||||
"home": self.download_dir,
|
"color": "no_color",
|
||||||
"temp": self.tempPath,
|
"paths": {"home": self.download_dir, "temp": self.tempPath},
|
||||||
},
|
"outtmpl": {"default": self.output_template, "chapter": self.output_template_chapter},
|
||||||
"outtmpl": {"default": self.output_template, "chapter": self.output_template_chapter},
|
"noprogress": True,
|
||||||
"noprogress": True,
|
"break_on_existing": True,
|
||||||
"break_on_existing": True,
|
"progress_hooks": [self._progress_hook],
|
||||||
"progress_hooks": [self._progress_hook],
|
"postprocessor_hooks": [self._postprocessor_hook],
|
||||||
"postprocessor_hooks": [self._postprocessor_hook],
|
"ignoreerrors": False,
|
||||||
**get_opts(self.preset, mergeConfig(self.default_ytdl_opts, self.ytdl_opts)),
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
if "format" not in params and self.default_ytdl_opts.get("format", None):
|
if "format" not in params and self.default_ytdl_opts.get("format", None):
|
||||||
params["format"] = self.default_ytdl_opts["format"]
|
params["format"] = "best"
|
||||||
|
|
||||||
params["ignoreerrors"] = False
|
|
||||||
|
|
||||||
if self.debug:
|
if self.debug:
|
||||||
params["verbose"] = True
|
params["verbose"] = True
|
||||||
|
|
@ -151,7 +149,7 @@ class Download:
|
||||||
|
|
||||||
params["cookiefile"] = f.name
|
params["cookiefile"] = f.name
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
LOG.error(f"Invalid cookies: was provided for {self.info.title} - {str(e)}")
|
LOG.error(f"Invalid cookies: was provided for '{self.info.title}'. '{str(e)}'.")
|
||||||
|
|
||||||
if self.is_live or self.is_manifestless:
|
if self.is_live or self.is_manifestless:
|
||||||
hasDeletedOptions = False
|
hasDeletedOptions = False
|
||||||
|
|
@ -176,7 +174,7 @@ class Download:
|
||||||
cls = yt_dlp.YoutubeDL(params=params)
|
cls = yt_dlp.YoutubeDL(params=params)
|
||||||
|
|
||||||
if isinstance(self.info_dict, dict) and len(self.info_dict) > 1:
|
if isinstance(self.info_dict, dict) and len(self.info_dict) > 1:
|
||||||
LOG.debug("Downloading using pre-info.")
|
LOG.debug(f"Downloading '{self.info.url}' using pre-info.")
|
||||||
cls.process_ie_result(self.info_dict, download=True)
|
cls.process_ie_result(self.info_dict, download=True)
|
||||||
ret = cls._download_retcode
|
ret = cls._download_retcode
|
||||||
else:
|
else:
|
||||||
|
|
@ -297,7 +295,7 @@ class Download:
|
||||||
|
|
||||||
if self.info.status != "finished" and self.is_live:
|
if self.info.status != "finished" and self.is_live:
|
||||||
LOG.warning(
|
LOG.warning(
|
||||||
f"Keeping live temp folder [{self.tempPath}], as the reported status is not finished [{self.info.status}]."
|
f"Keeping live temp folder '{self.tempPath}', as the reported status is not finished '{self.info.status}'."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -310,7 +308,7 @@ class Download:
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
LOG.info(f"Deleting Temp folder: {self.tempPath}")
|
LOG.info(f"Deleting Temp folder '{self.tempPath}'.")
|
||||||
shutil.rmtree(self.tempPath, ignore_errors=True)
|
shutil.rmtree(self.tempPath, ignore_errors=True)
|
||||||
|
|
||||||
async def progress_update(self):
|
async def progress_update(self):
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,8 @@ class DownloadQueue:
|
||||||
live_in: str | None = None
|
live_in: str | None = None
|
||||||
|
|
||||||
eventType = entry.get("_type") or "video"
|
eventType = entry.get("_type") or "video"
|
||||||
if eventType == "playlist":
|
if "playlist" == eventType:
|
||||||
entries = entry["entries"]
|
entries = entry.get("entries", [])
|
||||||
playlist_index_digits = len(str(len(entries)))
|
playlist_index_digits = len(str(len(entries)))
|
||||||
results = []
|
results = []
|
||||||
for i, etr in enumerate(entries, start=1):
|
for i, etr in enumerate(entries, start=1):
|
||||||
|
|
@ -106,16 +106,16 @@ class DownloadQueue:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if any(res["status"] == "error" for res in results):
|
if any("error" == res["status"] for res in results):
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"msg": ", ".join(res["msg"] for res in results if res["status"] == "error" and "msg" in res),
|
"msg": ", ".join(res["msg"] for res in results if res["status"] == "error" and "msg" in res),
|
||||||
}
|
}
|
||||||
|
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
elif (eventType == "video" or eventType.startswith("url")) and "id" in entry and "title" in entry:
|
elif ("video" == eventType or eventType.startswith("url")) and "id" in entry and "title" in entry:
|
||||||
# check if the video is live stream.
|
# check if the video is live stream.
|
||||||
if "live_status" in entry and entry.get("live_status") == "is_upcoming":
|
if "live_status" in entry and "is_upcoming" == entry.get("live_status"):
|
||||||
if "release_timestamp" in entry and entry.get("release_timestamp"):
|
if "release_timestamp" in entry and entry.get("release_timestamp"):
|
||||||
live_in = formatdate(entry.get("release_timestamp"))
|
live_in = formatdate(entry.get("release_timestamp"))
|
||||||
else:
|
else:
|
||||||
|
|
@ -230,10 +230,12 @@ class DownloadQueue:
|
||||||
already=None,
|
already=None,
|
||||||
):
|
):
|
||||||
ytdlp_config = ytdlp_config if ytdlp_config else {}
|
ytdlp_config = ytdlp_config if ytdlp_config else {}
|
||||||
folder = str(folder)
|
folder = str(folder) if folder else ""
|
||||||
|
|
||||||
|
filePath = calcDownloadPath(basePath=self.config.download_path, folder=folder)
|
||||||
|
|
||||||
LOG.info(
|
LOG.info(
|
||||||
f"Adding url '{url}' to folder '{folder}' with the following options 'Preset: {preset}' 'Naming: {output_template}', 'Cookies: {ytdlp_cookies}' 'YTConfig: {ytdlp_config}'."
|
f"Adding 'URL: {url}' to 'Folder: {filePath}' with 'Preset: {preset}' 'Naming: {output_template}', 'Cookies: {ytdlp_cookies}' 'YTConfig: {ytdlp_config}'."
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(ytdlp_config, str):
|
if isinstance(ytdlp_config, str):
|
||||||
|
|
@ -279,13 +281,13 @@ class DownloadQueue:
|
||||||
f"extract_info: for 'URL: {url}' is done in '{time.perf_counter() - started}'. Length: '{len(entry)}'."
|
f"extract_info: for 'URL: {url}' is done in '{time.perf_counter() - started}'. Length: '{len(entry)}'."
|
||||||
)
|
)
|
||||||
except yt_dlp.utils.ExistingVideoReached as exc:
|
except yt_dlp.utils.ExistingVideoReached as exc:
|
||||||
LOG.error(f"Video has been downloaded already and recorded in archive.log file. {str(exc)}")
|
LOG.error(f"Video has been downloaded already and recorded in archive.log file. '{str(exc)}'.")
|
||||||
return {"status": "error", "msg": "Video has been downloaded already and recorded in archive.log file."}
|
return {"status": "error", "msg": "Video has been downloaded already and recorded in archive.log file."}
|
||||||
except yt_dlp.utils.YoutubeDLError as exc:
|
except yt_dlp.utils.YoutubeDLError as exc:
|
||||||
LOG.error(f"YoutubeDLError: Unable to extract info. {str(exc)}")
|
LOG.error(f"YoutubeDLError: Unable to extract info. '{str(exc)}'.")
|
||||||
return {"status": "error", "msg": str(exc)}
|
return {"status": "error", "msg": str(exc)}
|
||||||
except asyncio.exceptions.TimeoutError as exc:
|
except asyncio.exceptions.TimeoutError as exc:
|
||||||
LOG.error(f"TimeoutError: Unable to extract info. {str(exc)}")
|
LOG.error(f"TimeoutError: Unable to extract info. '{str(exc)}'.")
|
||||||
return {
|
return {
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"msg": f"TimeoutError: {self.config.extract_info_timeout}s reached Unable to extract info.",
|
"msg": f"TimeoutError: {self.config.extract_info_timeout}s reached Unable to extract info.",
|
||||||
|
|
@ -366,7 +368,7 @@ class DownloadQueue:
|
||||||
os.remove(realFile)
|
os.remove(realFile)
|
||||||
fileDeleted = True
|
fileDeleted = True
|
||||||
else:
|
else:
|
||||||
LOG.warning(f"File '{filename}' does not exist.")
|
LOG.warning(f"Failed to remove '{itemRef}' local file '{filename}'. File not found.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error(f"Unable to remove '{itemRef}' local file '{filename}'. {str(e)}")
|
LOG.error(f"Unable to remove '{itemRef}' local file '{filename}'. {str(e)}")
|
||||||
|
|
||||||
|
|
@ -448,8 +450,9 @@ class DownloadQueue:
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
async def __downloadFile(self, id: str, entry: Download):
|
async def __downloadFile(self, id: str, entry: Download):
|
||||||
|
filePath = calcDownloadPath(basePath=self.config.download_path, folder=entry.info.folder)
|
||||||
LOG.info(
|
LOG.info(
|
||||||
f"Downloading 'id: {id}', 'Title: {entry.info.title}', 'URL: {entry.info.url}' to 'Folder: {entry.info.folder}'."
|
f"Downloading 'id: {id}', 'Title: {entry.info.title}', 'URL: {entry.info.url}' to 'Folder: {filePath}'."
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -222,24 +222,32 @@ class HttpAPI(common):
|
||||||
post = await request.json()
|
post = await request.json()
|
||||||
|
|
||||||
url: str = post.get("url")
|
url: str = post.get("url")
|
||||||
preset: str = post.get("preset", "default")
|
|
||||||
|
|
||||||
if not url:
|
if not url:
|
||||||
return web.json_response(data={"error": "url is required."}, status=web.HTTPBadRequest.status_code)
|
return web.json_response(data={"error": "url is required."}, status=web.HTTPBadRequest.status_code)
|
||||||
|
|
||||||
folder: str = post.get("folder")
|
preset: str = str(post.get("preset", self.config.default_preset))
|
||||||
ytdlp_cookies: str = post.get("ytdlp_cookies")
|
folder: str = str(post.get("folder")) if post.get("folder") else ""
|
||||||
ytdlp_config: dict | None = post.get("ytdlp_config")
|
ytdlp_cookies: str = str(post.get("ytdlp_cookies")) if post.get("ytdlp_cookies") else ""
|
||||||
output_template: str = post.get("output_template")
|
output_template: str = str(post.get("output_template")) if post.get("output_template") else ""
|
||||||
if ytdlp_config is None:
|
|
||||||
ytdlp_config = {}
|
ytdlp_config = post.get("ytdlp_config")
|
||||||
|
if isinstance(ytdlp_config, str) and ytdlp_config:
|
||||||
|
try:
|
||||||
|
ytdlp_config = json.loads(ytdlp_config)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(f"Failed to parse json yt-dlp config for '{url}'. {str(e)}")
|
||||||
|
return web.json_response(
|
||||||
|
data={"error": f"Failed to parse json yt-dlp config. {str(e)}"},
|
||||||
|
status=web.HTTPBadRequest.status_code,
|
||||||
|
)
|
||||||
|
|
||||||
status = await self.add(
|
status = await self.add(
|
||||||
url=url,
|
url=url,
|
||||||
preset=preset,
|
preset=preset,
|
||||||
folder=folder,
|
folder=folder,
|
||||||
ytdlp_cookies=ytdlp_cookies,
|
ytdlp_cookies=ytdlp_cookies,
|
||||||
ytdlp_config=ytdlp_config,
|
ytdlp_config=ytdlp_config if isinstance(ytdlp_config, dict) else {},
|
||||||
output_template=output_template,
|
output_template=output_template,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import errno
|
import errno
|
||||||
import functools
|
import functools
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pty
|
import pty
|
||||||
import shlex
|
import shlex
|
||||||
from datetime import datetime
|
|
||||||
import time
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import socketio
|
import socketio
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
@ -161,20 +162,25 @@ class HttpSocket(common):
|
||||||
await self.emitter.warning("No URL provided.", to=sid)
|
await self.emitter.warning("No URL provided.", to=sid)
|
||||||
return
|
return
|
||||||
|
|
||||||
preset: str = str(data.get("preset", "default"))
|
preset: str = str(data.get("preset", self.config.default_preset))
|
||||||
folder: str = str(data.get("folder"))
|
folder: str = str(data.get("folder")) if data.get("folder") else ""
|
||||||
ytdlp_cookies: str = str(data.get("ytdlp_cookies"))
|
ytdlp_cookies: str = str(data.get("ytdlp_cookies")) if data.get("ytdlp_cookies") else ""
|
||||||
ytdlp_config: dict | None = data.get("ytdlp_config")
|
output_template: str = str(data.get("output_template")) if data.get("output_template") else ""
|
||||||
output_template: str = data.get("output_template")
|
|
||||||
if ytdlp_config is None:
|
ytdlp_config = data.get("ytdlp_config")
|
||||||
ytdlp_config = {}
|
if isinstance(ytdlp_config, str) and ytdlp_config:
|
||||||
|
try:
|
||||||
|
ytdlp_config = json.loads(ytdlp_config)
|
||||||
|
except Exception as e:
|
||||||
|
await self.emitter.warning(f"Failed to parse json yt-dlp config. {str(e)}", to=sid)
|
||||||
|
return
|
||||||
|
|
||||||
status = await self.add(
|
status = await self.add(
|
||||||
url=url,
|
url=url,
|
||||||
preset=preset,
|
preset=preset,
|
||||||
folder=folder,
|
folder=folder,
|
||||||
ytdlp_cookies=ytdlp_cookies,
|
ytdlp_cookies=ytdlp_cookies,
|
||||||
ytdlp_config=ytdlp_config,
|
ytdlp_config=ytdlp_config if isinstance(ytdlp_config, dict) else {},
|
||||||
output_template=output_template,
|
output_template=output_template,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ def getVideoInfo(url: str, ytdlp_opts: dict = None, no_archive: bool = True) ->
|
||||||
return yt_dlp.YoutubeDL().extract_info(url, download=False)
|
return yt_dlp.YoutubeDL().extract_info(url, download=False)
|
||||||
|
|
||||||
|
|
||||||
def calcDownloadPath(basePath: str, folder: str = None, createPath: bool = True) -> str:
|
def calcDownloadPath(basePath: str, folder: str|None = None, createPath: bool = True) -> str:
|
||||||
"""Calculates download path and prevents folder traversal.
|
"""Calculates download path and prevents folder traversal.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,6 @@ class common:
|
||||||
async def add(
|
async def add(
|
||||||
self, url: str, preset: str, folder: str, ytdlp_cookies: str, ytdlp_config: dict, output_template: str
|
self, url: str, preset: str, folder: str, ytdlp_cookies: str, ytdlp_config: dict, output_template: str
|
||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
if ytdlp_config is None:
|
|
||||||
ytdlp_config = {}
|
|
||||||
|
|
||||||
if ytdlp_cookies and isinstance(ytdlp_cookies, dict):
|
if ytdlp_cookies and isinstance(ytdlp_cookies, dict):
|
||||||
ytdlp_cookies = self.encoder.encode(ytdlp_cookies)
|
ytdlp_cookies = self.encoder.encode(ytdlp_cookies)
|
||||||
|
|
||||||
|
|
@ -33,7 +30,7 @@ class common:
|
||||||
preset=preset if preset else "default",
|
preset=preset if preset else "default",
|
||||||
folder=folder,
|
folder=folder,
|
||||||
ytdlp_cookies=ytdlp_cookies,
|
ytdlp_cookies=ytdlp_cookies,
|
||||||
ytdlp_config=ytdlp_config,
|
ytdlp_config=ytdlp_config if isinstance(ytdlp_config, dict) else {},
|
||||||
output_template=output_template,
|
output_template=output_template,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,8 @@ export const useSocketStore = defineStore('socket', () => {
|
||||||
socket.value.on("updated", stream => {
|
socket.value.on("updated", stream => {
|
||||||
const data = JSON.parse(stream);
|
const data = JSON.parse(stream);
|
||||||
|
|
||||||
if (true === stateStore.has('history', item._id)) {
|
if (true === stateStore.has('history', data._id)) {
|
||||||
stateStore.update('history', item._id, item);
|
stateStore.update('history', data._id, data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue