commit
2515f4edd6
7 changed files with 105 additions and 48 deletions
31
app/Utils.py
31
app/Utils.py
|
|
@ -4,6 +4,8 @@ from datetime import datetime, timezone
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
from typing import Any
|
||||
import yt_dlp
|
||||
from socketio import AsyncServer
|
||||
|
|
@ -388,3 +390,32 @@ def load_file(file: str, check_type=None) -> tuple[dict | list, bool, str]:
|
|||
return ({}, False, f"Failed to assert that the contents '{type(opts)}' are of type '{check_type}'.",)
|
||||
except Exception as e:
|
||||
return ({}, False, f'{e}',)
|
||||
|
||||
|
||||
def checkId(basePath: str, file: pathlib.Path) -> bool | str:
|
||||
"""
|
||||
Check if we are able to get an id from the file name.
|
||||
if so check if any video file with the same id exists.
|
||||
|
||||
:param basePath: Base path to strip.
|
||||
:param file: File to check.
|
||||
|
||||
:return: False if no id found, otherwise the id.
|
||||
"""
|
||||
|
||||
match = re.search(r'(?<=\[)(?:youtube-)?(?P<id>[a-zA-Z0-9\-_]{11})(?=\])', file.stem, re.IGNORECASE)
|
||||
if not match:
|
||||
return False
|
||||
|
||||
id = match.groupdict().get('id')
|
||||
|
||||
for f in file.parent.iterdir():
|
||||
if id not in f.stem:
|
||||
continue
|
||||
|
||||
if f.suffix not in ('.mp4', '.mkv', '.webm', '.m4v', '.m4a', '.mp3', '.aac', '.ogg',):
|
||||
continue
|
||||
|
||||
return f.absolute()
|
||||
|
||||
return False
|
||||
|
|
|
|||
21
app/main.py
21
app/main.py
|
|
@ -465,6 +465,8 @@ class Main:
|
|||
download_path=self.config.download_path,
|
||||
file=file
|
||||
)
|
||||
if isinstance(text, Response):
|
||||
return text
|
||||
except Exception as e:
|
||||
return web.HTTPNotFound(reason=str(e))
|
||||
|
||||
|
|
@ -519,9 +521,11 @@ class Main:
|
|||
sd: int = request.query.get('sd')
|
||||
vc: int = int(request.query.get('vc', 0))
|
||||
ac: int = int(request.query.get('ac', 0))
|
||||
file_path: str = os.path.normpath(os.path.join(self.config.download_path, file))
|
||||
if not file_path.startswith(self.config.download_path):
|
||||
raise web.HTTPBadRequest(reason='Invalid file path.')
|
||||
|
||||
if request.if_modified_since:
|
||||
file_path = os.path.join(self.config.download_path, file)
|
||||
if os.path.exists(file_path) and request.if_modified_since.timestamp() == os.path.getmtime(file_path):
|
||||
return web.Response(status=304)
|
||||
|
||||
|
|
@ -546,9 +550,7 @@ class Main:
|
|||
'Access-Control-Allow-Origin': '*',
|
||||
'Pragma': 'public',
|
||||
'Cache-Control': f'public, max-age={time.time() + 31536000}',
|
||||
'Last-Modified': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(
|
||||
os.path.getmtime(os.path.join(self.config.download_path, file))).timetuple()
|
||||
),
|
||||
'Last-Modified': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(os.path.getmtime(file_path)).timetuple()),
|
||||
'Expires': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(time.time() + 31536000).timetuple()),
|
||||
}
|
||||
)
|
||||
|
|
@ -556,12 +558,13 @@ class Main:
|
|||
@self.routes.get(self.config.url_prefix + 'player/subtitle/{file:.*}.vtt')
|
||||
async def subtitles(request: Request) -> Response:
|
||||
file: str = request.match_info.get('file')
|
||||
file_path: str = os.path.normpath(os.path.join(self.config.download_path, file))
|
||||
if not file_path.startswith(self.config.download_path):
|
||||
raise web.HTTPBadRequest(reason='Invalid file path.')
|
||||
|
||||
if request.if_modified_since:
|
||||
file_path = os.path.join(self.config.download_path, file)
|
||||
lastMod = time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(
|
||||
os.path.getmtime(os.path.join(self.config.download_path, file))).timetuple()
|
||||
)
|
||||
os.path.getmtime(file_path)).timetuple())
|
||||
if os.path.exists(file_path) and request.if_modified_since.timestamp() == os.path.getmtime(file_path):
|
||||
return web.Response(status=304, headers={'Last-Modified': lastMod})
|
||||
|
||||
|
|
@ -576,9 +579,7 @@ class Main:
|
|||
'Access-Control-Allow-Origin': '*',
|
||||
'Pragma': 'public',
|
||||
'Cache-Control': f'public, max-age={time.time() + 31536000}',
|
||||
'Last-Modified': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(
|
||||
os.path.getmtime(os.path.join(self.config.download_path, file))).timetuple()
|
||||
),
|
||||
'Last-Modified': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(os.path.getmtime(file_path)).timetuple()),
|
||||
'Expires': time.strftime('%a, %d %b %Y %H:%M:%S GMT', datetime.fromtimestamp(time.time() + 31536000).timetuple()),
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ import glob
|
|||
import pathlib
|
||||
import re
|
||||
from urllib.parse import quote
|
||||
from Utils import calcDownloadPath
|
||||
from Utils import calcDownloadPath, checkId
|
||||
import pathlib
|
||||
from .ffprobe import FFProbe
|
||||
from .Subtitle import Subtitle
|
||||
from aiohttp.web import Response
|
||||
|
||||
|
||||
class Playlist:
|
||||
|
|
@ -14,11 +15,17 @@ class Playlist:
|
|||
def __init__(self, url: str):
|
||||
self.url = url
|
||||
|
||||
async def make(self, download_path: str, file: str):
|
||||
async def make(self, download_path: str, file: str) -> str | Response:
|
||||
rFile = pathlib.Path(calcDownloadPath(basePath=download_path, folder=file, createPath=False))
|
||||
|
||||
if not rFile.exists():
|
||||
raise Exception(f"File '{rFile}' does not exist.")
|
||||
possibleFile = checkId(download_path, rFile)
|
||||
if not possibleFile:
|
||||
raise Exception(f"File '{rFile}' does not exist.")
|
||||
|
||||
return Response(status=302, headers={
|
||||
'Location': f"{self.url}player/playlist/{quote(str(possibleFile).replace(download_path, '').strip('/'))}.m3u8"
|
||||
})
|
||||
|
||||
try:
|
||||
ffprobe = FFProbe(rFile)
|
||||
|
|
|
|||
65
frontend/package-lock.json
generated
65
frontend/package-lock.json
generated
|
|
@ -2239,6 +2239,17 @@
|
|||
"@types/json-schema": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint-scope": {
|
||||
"version": "3.7.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
|
||||
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/eslint": "*",
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
|
|
@ -2331,9 +2342,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.6.tgz",
|
||||
"integrity": "sha512-tosuJYKrIqjQIlVCM4PEGxOmyg3FCPa/fViuJChnGeEIhjA46oy8FMVoF9su1/v8PNs2a8Q0iFNyOx0uOF91nw==",
|
||||
"version": "22.8.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.7.tgz",
|
||||
"integrity": "sha512-LidcG+2UeYIWcMuMUpBKOnryBWG/rnmOHQR5apjn8myTQcx3rinFRn7DcIFhMnS0PPFSC6OafdIKEad0lj6U0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -2435,9 +2446,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz",
|
||||
"integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==",
|
||||
"version": "8.5.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
|
||||
"integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -3468,16 +3479,6 @@
|
|||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-import-attributes": {
|
||||
"version": "1.9.5",
|
||||
"resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz",
|
||||
"integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"acorn": "^8"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-jsx": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||
|
|
@ -4164,9 +4165,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001676",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001676.tgz",
|
||||
"integrity": "sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw==",
|
||||
"version": "1.0.30001677",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001677.tgz",
|
||||
"integrity": "sha512-fmfjsOlJUpMWu+mAAtZZZHz7UEwsUxIIvu1TJfO1HqFQvB/B+ii0xr9B5HpbZY/mC4XZ8SvjHJqtAY6pDPQEog==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -8174,9 +8175,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/mini-css-extract-plugin": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.1.tgz",
|
||||
"integrity": "sha512-+Vyi+GCCOHnrJ2VPS+6aPoXN2k2jgUzDRhTFLjjTBn23qyXJXkjUWQgTL+mXpF5/A8ixLdCc6kWsoeOjKGejKQ==",
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz",
|
||||
"integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -11203,9 +11204,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz",
|
||||
"integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==",
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
|
|
@ -11675,19 +11676,19 @@
|
|||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.95.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz",
|
||||
"integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==",
|
||||
"version": "5.96.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz",
|
||||
"integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.5",
|
||||
"@types/eslint-scope": "^3.7.7",
|
||||
"@types/estree": "^1.0.6",
|
||||
"@webassemblyjs/ast": "^1.12.1",
|
||||
"@webassemblyjs/wasm-edit": "^1.12.1",
|
||||
"@webassemblyjs/wasm-parser": "^1.12.1",
|
||||
"acorn": "^8.7.1",
|
||||
"acorn-import-attributes": "^1.9.5",
|
||||
"browserslist": "^4.21.10",
|
||||
"acorn": "^8.14.0",
|
||||
"browserslist": "^4.24.0",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.17.1",
|
||||
"es-module-lexer": "^1.2.1",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<div class="modal-background"></div>
|
||||
<div class="modal-content">
|
||||
<VideoPlayer type="default" :link="video_link" :isMuted="false" autoplay="true" :isControls="true"
|
||||
class="is-fullwidth" @closeModel="video_link = ''" />
|
||||
:title="video_title" class="is-fullwidth" @closeModel="video_link = ''; video_title = ''" />
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" @click="video_link = ''"></button>
|
||||
</div>
|
||||
|
|
@ -53,6 +53,7 @@ const socket = ref()
|
|||
const downloading = reactive({})
|
||||
const completed = reactive({})
|
||||
const video_link = ref('')
|
||||
const video_title = ref('')
|
||||
const addForm = useStorage('addForm', true)
|
||||
const showTasks = useStorage('showTasks', false)
|
||||
const cli_output = ref([])
|
||||
|
|
@ -231,7 +232,10 @@ const addItem = (item) => {
|
|||
});
|
||||
};
|
||||
|
||||
const playItem = item => video_link.value = makeDownload(config, item, 'm3u8');
|
||||
const playItem = item => {
|
||||
video_link.value = makeDownload(config, item, 'm3u8');
|
||||
video_title.value = item.title;
|
||||
}
|
||||
|
||||
const reloadWindow = () => window.location.reload();
|
||||
|
||||
|
|
|
|||
|
|
@ -77,7 +77,13 @@ onUnmounted(() => {
|
|||
if (hls) {
|
||||
hls.destroy()
|
||||
}
|
||||
|
||||
window.removeEventListener('keydown', eventFunc)
|
||||
|
||||
if (props.title) {
|
||||
window.document.title = 'YTPTube'
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
const prepareVideoPlayer = () => {
|
||||
|
|
@ -97,6 +103,7 @@ const prepareVideoPlayer = () => {
|
|||
enabled: true,
|
||||
key: 'plyr'
|
||||
},
|
||||
title: props.title,
|
||||
mediaMetadata: {
|
||||
title: props.title
|
||||
},
|
||||
|
|
@ -118,5 +125,11 @@ const prepareVideoPlayer = () => {
|
|||
if (video.value) {
|
||||
hls.attachMedia(video.value)
|
||||
}
|
||||
|
||||
if (props.title) {
|
||||
window.document.title = `YTPTube - Playing: ${props.title}`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ app.config.globalProperties.makeDownload = (config, item, base = 'download') =>
|
|||
let baseDir = 'download' === base ? `${base}/` : 'player/playlist/';
|
||||
|
||||
if (item.folder) {
|
||||
item.folder = item.folder.replace('#', '%23');
|
||||
item.folder = item.folder.replace(/#/g, '%23');
|
||||
baseDir += item.folder + '/';
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue