Minor Changes

This commit is contained in:
TylerCG 2025-04-27 18:54:51 -04:00
parent ecfeb5927d
commit 76042548eb
6 changed files with 292 additions and 107 deletions

2
.vscode/launch.json vendored
View File

@ -13,7 +13,7 @@
"jinja": true, "jinja": true,
"justMyCode": true, "justMyCode": true,
"env": { "env": {
"PYTHONPATH": "${workspaceFolder}/app" "PYTHONPATH": "${workspaceFolder}/app",
} }
} }
] ]

View File

@ -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():

View File

@ -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)})

View File

@ -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">&#9733;</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
View 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>

View File

@ -5,4 +5,5 @@ fastapi
pathlib pathlib
uvicorn uvicorn
jinja2 jinja2
python-multipart python-multipart
selenium