Minor Changes
This commit is contained in:
parent
ecfeb5927d
commit
76042548eb
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -13,7 +13,7 @@
|
|||||||
"jinja": true,
|
"jinja": true,
|
||||||
"justMyCode": true,
|
"justMyCode": true,
|
||||||
"env": {
|
"env": {
|
||||||
"PYTHONPATH": "${workspaceFolder}/app"
|
"PYTHONPATH": "${workspaceFolder}/app",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import os, yt_dlp, json, requests, re
|
import os, yt_dlp, json, requests, re, time
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.keys import Keys
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
@ -12,38 +14,23 @@ class grab():
|
|||||||
seasons = [item.replace(url+'/season:', '') for item in option_values]
|
seasons = [item.replace(url+'/season:', '') for item in option_values]
|
||||||
return seasons
|
return seasons
|
||||||
|
|
||||||
def poster(url, save_dir='/data/posters/', force_download=False):
|
def poster(url, name, save_dir='/data/posters/', force_download=False):
|
||||||
page_html = requests.get(url)
|
# Use alt for filename if available, fallback to a generic name
|
||||||
soup = BeautifulSoup(page_html.text, 'html.parser')
|
alt_value = name
|
||||||
|
path = urlsplit(url).path
|
||||||
|
ext = os.path.splitext(path)[-1] or '.jpeg'
|
||||||
|
|
||||||
# Find the first <img> anywhere inside a .product-feature
|
safe_name = re.sub(r'[^a-zA-Z0-9\s]', '', alt_value).replace(' ', '_')
|
||||||
feature_section = soup.find(class_='product-feature')
|
filename = f"{safe_name}{ext}"
|
||||||
if not feature_section:
|
filepath = os.path.join(save_dir, filename)
|
||||||
return None
|
|
||||||
|
|
||||||
img_tag = feature_section.find('img', attrs={'data-image-primary': True}) or feature_section.find('img')
|
if not os.path.exists(filepath) or force_download:
|
||||||
|
os.makedirs(save_dir, exist_ok=True)
|
||||||
if img_tag and img_tag.has_attr('src'):
|
img_data = requests.get(url).content
|
||||||
img_url = img_tag['src']
|
with open(filepath, 'wb') as handler:
|
||||||
|
handler.write(img_data)
|
||||||
# Use alt for filename if available, fallback to a generic name
|
|
||||||
alt_value = img_tag.get('alt', 'image')
|
|
||||||
path = urlsplit(img_url).path
|
|
||||||
ext = os.path.splitext(path)[-1] or '.jpeg'
|
|
||||||
|
|
||||||
safe_name = re.sub(r'[^a-zA-Z0-9\s]', '', alt_value).replace(' ', '_')
|
|
||||||
filename = f"{safe_name}{ext}"
|
|
||||||
filepath = os.path.join(save_dir, filename)
|
|
||||||
|
|
||||||
if not os.path.exists(filepath) or force_download:
|
return filepath
|
||||||
os.makedirs(save_dir, exist_ok=True)
|
|
||||||
img_data = requests.get(img_url).content
|
|
||||||
with open(filepath, 'wb') as handler:
|
|
||||||
handler.write(img_data)
|
|
||||||
|
|
||||||
return filepath
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def thumbnail(ydl,url,location):
|
def thumbnail(ydl,url,location):
|
||||||
@ -191,26 +178,43 @@ class dropout():
|
|||||||
|
|
||||||
def series():
|
def series():
|
||||||
json_data=[]
|
json_data=[]
|
||||||
page_html=requests.get('https://www.dropout.tv/series')
|
# driver = webdriver.Chrome(executable_path='/path/to/chromedriver')
|
||||||
# If you want to parse the HTML
|
# driver.get('https://www.dropout.tv/series')
|
||||||
soup = BeautifulSoup(page_html.text, 'html.parser')
|
# for _ in range(5): # Adjust the range as needed
|
||||||
# Example: Find all elements with a specific class
|
# driver.find_element_by_tag_name('body').send_keys(Keys.END)
|
||||||
elements = soup.find_all(class_='browse-item-link')
|
# time.sleep(2) # Wait for new content to load
|
||||||
# Extract URLs from href attributes
|
# html = driver.page_source
|
||||||
urls = [element['href'] for element in elements if 'href' in element.attrs]
|
|
||||||
|
|
||||||
for url in urls:
|
html=requests.get('https://www.dropout.tv/series').text
|
||||||
info_data={}
|
|
||||||
name=url.replace('https://www.dropout.tv/','').replace('-s-',"'s-").replace('-',' ').title().replace('Of','of').replace("'S","'s")
|
# If you want to parse the HTML
|
||||||
info_data['SHOW'] = name
|
soup = BeautifulSoup(html, 'html.parser')
|
||||||
info_data['URL'] = url
|
elements = soup.find_all('a', class_='browse-item-link')
|
||||||
info_data['POSTER'] = grab.poster(url)
|
|
||||||
info_data['SEASONS'] = grab.season(url)
|
shows = []
|
||||||
|
for element in elements:
|
||||||
|
show_data = {}
|
||||||
|
show_data['href'] = element.get('href', '')
|
||||||
|
|
||||||
|
img = element.find('img')
|
||||||
|
if img:
|
||||||
|
show_data['src'] = img.get('src', '')
|
||||||
|
show_data['alt'] = img.get('alt', '')
|
||||||
|
|
||||||
|
shows.append(show_data)
|
||||||
|
|
||||||
|
# Now 'shows' is a list of dicts, so this works:
|
||||||
|
for show in shows:
|
||||||
|
info_data = {}
|
||||||
|
info_data['SHOW'] = show.get('alt', 'No title')
|
||||||
|
info_data['URL'] = show.get('href', 'No link')
|
||||||
|
info_data['LINK'] = re.sub(r".*dropout.tv/", "", show.get('href', ''))
|
||||||
|
info_data['POSTER'] = grab.poster(show.get('src', ''), show.get('alt', ''))
|
||||||
json_data.append(info_data)
|
json_data.append(info_data)
|
||||||
|
|
||||||
# Sort the json_data by the 'SHOW' key
|
# Sort the json_data by the 'SHOW' key
|
||||||
sorted_json_data = sorted(json_data, key=lambda x: x['SHOW'])
|
sorted_json_data = sorted(json_data, key=lambda x: x['SHOW'])
|
||||||
with open('/data/dropout.json', 'w') as json_file:
|
with open('./data/dropout.json', 'w') as json_file:
|
||||||
json.dump(sorted_json_data, json_file, indent=4)
|
json.dump(sorted_json_data, json_file, indent=4)
|
||||||
|
|
||||||
class youtube():
|
class youtube():
|
||||||
|
|||||||
60
app/main.py
60
app/main.py
@ -7,32 +7,38 @@ from pathlib import Path
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
import json, download, asyncio
|
import json, download, asyncio
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
import os
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
# app.mount("/static", StaticFiles(directory="/app/app/static"), name="static")
|
|
||||||
app.mount("/data", StaticFiles(directory="/data"), name="data")
|
app.mount("/data", StaticFiles(directory="/data"), name="data")
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
|
||||||
|
templates = Jinja2Templates(directory="app/templates")
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
|
# JSON cache
|
||||||
|
cached_data = None
|
||||||
|
|
||||||
# api
|
# api
|
||||||
@app.get("/dropout/update")
|
@app.get("/dropout/update")
|
||||||
async def dropoutUpdate():
|
async def dropoutUpdate():
|
||||||
|
global cached_data
|
||||||
try:
|
try:
|
||||||
download.dropout.series()
|
download.dropout.series()
|
||||||
|
with open('/data/dropout.json') as f:
|
||||||
|
cached_data = json.load(f)
|
||||||
return JSONResponse(status_code=200, content={"status": "success", "message": "Series grab complete."})
|
return JSONResponse(status_code=200, content={"status": "success", "message": "Series grab complete."})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
|
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
|
||||||
|
|
||||||
@app.get("/dropout/series")
|
@app.get("/dropout/series")
|
||||||
async def dropoutSeries():
|
async def dropoutSeries():
|
||||||
file_path = Path("/data/dropout.json")
|
global cached_data
|
||||||
if file_path.exists():
|
if cached_data is None:
|
||||||
with file_path.open("r", encoding="utf-8") as f:
|
await dropoutUpdate()
|
||||||
data = json.load(f)
|
return JSONResponse(content=cached_data)
|
||||||
return JSONResponse(content=data)
|
|
||||||
return JSONResponse(content={"error": "File not found"}, status_code=404)
|
return JSONResponse(content={"error": "File not found"}, status_code=404)
|
||||||
|
|
||||||
@app.post("/dropout/download", description="Download an entire season from episode 1. Ignores behind the scenes and trailers.")
|
@app.post("/dropout/download", description="Download an entire season from episode 1. Ignores behind the scenes and trailers.")
|
||||||
@ -70,25 +76,29 @@ async def ydl(url: str, location: str):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
|
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
|
||||||
|
|
||||||
# html
|
|
||||||
# @app.get("/", include_in_schema=False, response_class=HTMLResponse)
|
|
||||||
# async def index(request: Request):
|
|
||||||
# apps = [
|
|
||||||
# {"name": "Notes", "url": "/notes"},
|
|
||||||
# {"name": "Todo List", "url": "/todos"},
|
|
||||||
# {"name": "Weather", "url": "/weather"},
|
|
||||||
# # Add more apps here
|
|
||||||
# ]
|
|
||||||
# return templates.TemplateResponse("index.html", {"request": request, "apps": apps, "title": "Welcome to My App Hub"})
|
|
||||||
|
|
||||||
|
|
||||||
# JSON cache
|
|
||||||
cached_data = None
|
|
||||||
|
|
||||||
@app.get("/", response_class=HTMLResponse)
|
@app.get("/", response_class=HTMLResponse)
|
||||||
async def index(request: Request):
|
async def index(request: Request):
|
||||||
global cached_data
|
global cached_data
|
||||||
if cached_data is None:
|
try:
|
||||||
with open('/data/dropout.json') as f:
|
if cached_data is None:
|
||||||
cached_data = json.load(f)
|
await dropoutUpdate()
|
||||||
return templates.TemplateResponse("index.html", {"request": request, "data": cached_data})
|
return templates.TemplateResponse("index.html", {"request": request, "data": cached_data})
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
|
||||||
|
|
||||||
|
@app.get("/show/{show}", include_in_schema=False, response_class=HTMLResponse)
|
||||||
|
async def index(request: Request, show = str):
|
||||||
|
try:
|
||||||
|
global cached_data
|
||||||
|
if cached_data is None:
|
||||||
|
await dropoutUpdate()
|
||||||
|
try:
|
||||||
|
for item in cached_data:
|
||||||
|
if show == item['LINK']:
|
||||||
|
show_data = item
|
||||||
|
return templates.TemplateResponse("show.html", {"request": request, "show": show_data})
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
|
||||||
@ -1,40 +1,164 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Show Posters</title>
|
<title>Show Posters</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
background-color: #141414 !important;
|
||||||
.tile {
|
}
|
||||||
width: 400px;
|
|
||||||
margin: 10px;
|
.tile {
|
||||||
text-align: center;
|
width: 400px;
|
||||||
border: 1px solid #ddd;
|
margin: 10px;
|
||||||
border-radius: 8px;
|
text-align: center;
|
||||||
overflow: hidden;
|
border: 1px solid #030000;
|
||||||
}
|
border-radius: 8px;
|
||||||
.tile img {
|
overflow: hidden;
|
||||||
width: 100%;
|
box-shadow: #00000069 0px 5px 10px;
|
||||||
height: auto;
|
transition: all 0.2s ease;
|
||||||
}
|
position: relative;
|
||||||
.tile .title {
|
}
|
||||||
padding: 10px;
|
|
||||||
background-color: #f4f4f4;
|
.tile:hover {
|
||||||
}
|
transform: scale(1.05);
|
||||||
</style>
|
box-shadow: #00000069 0px 10px 15px;
|
||||||
</head>
|
}
|
||||||
<body>
|
|
||||||
{% for item in data %}
|
.tile img {
|
||||||
<div class="tile">
|
width: 100%;
|
||||||
<img src="{{ item['POSTER'] }}" alt="{{ item['SHOW'] }}">
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile .title {
|
||||||
|
padding: 10px;
|
||||||
|
color: #eeeeee;
|
||||||
|
background-color: #030000be;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(100%);
|
||||||
|
opacity: 0;
|
||||||
|
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tile:hover .title {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make the star hidden by default */
|
||||||
|
.favorite-star {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
right: 8px;
|
||||||
|
font-size: 30px;
|
||||||
|
color: #ffffff;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0; /* Hide the star */
|
||||||
|
transition: opacity 0.3s ease; /* Smooth transition */
|
||||||
|
text-shadow: #000000 1px 1px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the star on hover */
|
||||||
|
.tile:hover .favorite-star {
|
||||||
|
opacity: 1; /* Show the star when the tile is hovered */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the favorited star */
|
||||||
|
.favorite-star.favorited {
|
||||||
|
color: gold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% for item in data %}
|
||||||
|
<div class="tile" data-id="{{ item['ID'] }}">
|
||||||
|
<!-- Make the image clickable by wrapping it in an anchor tag -->
|
||||||
|
<a href="/show/{{ item['LINK'] }}">
|
||||||
|
<img src="{{ item['POSTER'] }}" alt="{{ item['SHOW'] }}">
|
||||||
|
</a>
|
||||||
<div class="title">{{ item['SHOW'] }}</div>
|
<div class="title">{{ item['SHOW'] }}</div>
|
||||||
|
<div class="favorite-star">★</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</body>
|
|
||||||
|
<!-- <script>
|
||||||
|
// Load favorites from localStorage
|
||||||
|
const favorites = JSON.parse(localStorage.getItem('favorites')) || [];
|
||||||
|
|
||||||
|
// Function to update favorite status based on stored data
|
||||||
|
function updateFavoriteStatus() {
|
||||||
|
const tiles = document.querySelectorAll('.tile');
|
||||||
|
tiles.forEach(tile => {
|
||||||
|
const id = tile.getAttribute('data-id');
|
||||||
|
const star = tile.querySelector('.favorite-star');
|
||||||
|
|
||||||
|
// Add "favorited" class if item is in favorites
|
||||||
|
if (favorites.includes(id)) {
|
||||||
|
star.classList.add('favorited');
|
||||||
|
} else {
|
||||||
|
star.classList.remove('favorited');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listeners for the favorite stars
|
||||||
|
document.querySelectorAll('.favorite-star').forEach(star => {
|
||||||
|
star.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation(); // Prevent the click from bubbling up to the tile
|
||||||
|
|
||||||
|
const tile = e.target.closest('.tile');
|
||||||
|
const id = tile.getAttribute('data-id');
|
||||||
|
|
||||||
|
// Toggle favorite
|
||||||
|
if (favorites.includes(id)) {
|
||||||
|
favorites.splice(favorites.indexOf(id), 1); // Remove from favorites
|
||||||
|
} else {
|
||||||
|
favorites.push(id); // Add to favorites
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the updated favorites list to localStorage
|
||||||
|
localStorage.setItem('favorites', JSON.stringify(favorites));
|
||||||
|
|
||||||
|
// Update the tile's favorite status
|
||||||
|
updateFavoriteStatus();
|
||||||
|
|
||||||
|
// Reorder items: Move favorites to the top
|
||||||
|
reorderItems();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to reorder items with favorites at the top
|
||||||
|
function reorderItems() {
|
||||||
|
const tiles = document.querySelectorAll('.tile');
|
||||||
|
const tilesArray = Array.from(tiles);
|
||||||
|
|
||||||
|
// Sort tiles: favorites first
|
||||||
|
tilesArray.sort((a, b) => {
|
||||||
|
const idA = a.getAttribute('data-id');
|
||||||
|
const idB = b.getAttribute('data-id');
|
||||||
|
const isAFavorite = favorites.includes(idA);
|
||||||
|
const isBFavorite = favorites.includes(idB);
|
||||||
|
return isBFavorite - isAFavorite;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Append tiles back in the new order
|
||||||
|
const container = document.body;
|
||||||
|
tilesArray.forEach(tile => container.appendChild(tile));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initially update the favorite status and reorder items
|
||||||
|
updateFavoriteStatus();
|
||||||
|
reorderItems();
|
||||||
|
</script> -->
|
||||||
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
46
app/templates/show.html
Normal file
46
app/templates/show.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{{ show['SHOW'] }}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #141414 !important;
|
||||||
|
}
|
||||||
|
/* Add any styles you want */
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #555;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<img src="{{ show['POSTER'] }}" alt="{{ show['SHOW'] }}">
|
||||||
|
<div class="title">{{ show['SHOW'] }}</div>
|
||||||
|
<div class="description">
|
||||||
|
{{ show['DESCRIPTION'] }}
|
||||||
|
</div>
|
||||||
|
<!-- Optional back link -->
|
||||||
|
<p><a href="/">← Back to all shows</a></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -5,4 +5,5 @@ fastapi
|
|||||||
pathlib
|
pathlib
|
||||||
uvicorn
|
uvicorn
|
||||||
jinja2
|
jinja2
|
||||||
python-multipart
|
python-multipart
|
||||||
|
selenium
|
||||||
Loading…
x
Reference in New Issue
Block a user