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,
"justMyCode": true,
"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 urllib.parse import urlsplit
@ -12,38 +14,23 @@ class grab():
seasons = [item.replace(url+'/season:', '') for item in option_values]
return seasons
def poster(url, save_dir='/data/posters/', force_download=False):
page_html = requests.get(url)
soup = BeautifulSoup(page_html.text, 'html.parser')
def poster(url, name, save_dir='/data/posters/', force_download=False):
# Use alt for filename if available, fallback to a generic name
alt_value = name
path = urlsplit(url).path
ext = os.path.splitext(path)[-1] or '.jpeg'
# Find the first <img> anywhere inside a .product-feature
feature_section = soup.find(class_='product-feature')
if not feature_section:
return None
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)
img_tag = feature_section.find('img', attrs={'data-image-primary': True}) or feature_section.find('img')
if img_tag and img_tag.has_attr('src'):
img_url = img_tag['src']
# 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:
os.makedirs(save_dir, exist_ok=True)
img_data = requests.get(url).content
with open(filepath, 'wb') as handler:
handler.write(img_data)
if not os.path.exists(filepath) or force_download:
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
return filepath
def thumbnail(ydl,url,location):
@ -191,26 +178,43 @@ class dropout():
def series():
json_data=[]
page_html=requests.get('https://www.dropout.tv/series')
# If you want to parse the HTML
soup = BeautifulSoup(page_html.text, 'html.parser')
# Example: Find all elements with a specific class
elements = soup.find_all(class_='browse-item-link')
# Extract URLs from href attributes
urls = [element['href'] for element in elements if 'href' in element.attrs]
# driver = webdriver.Chrome(executable_path='/path/to/chromedriver')
# driver.get('https://www.dropout.tv/series')
# for _ in range(5): # Adjust the range as needed
# driver.find_element_by_tag_name('body').send_keys(Keys.END)
# time.sleep(2) # Wait for new content to load
# html = driver.page_source
for url in urls:
info_data={}
name=url.replace('https://www.dropout.tv/','').replace('-s-',"'s-").replace('-',' ').title().replace('Of','of').replace("'S","'s")
info_data['SHOW'] = name
info_data['URL'] = url
info_data['POSTER'] = grab.poster(url)
info_data['SEASONS'] = grab.season(url)
html=requests.get('https://www.dropout.tv/series').text
# If you want to parse the HTML
soup = BeautifulSoup(html, 'html.parser')
elements = soup.find_all('a', class_='browse-item-link')
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)
# Sort the json_data by the 'SHOW' key
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)
class youtube():

View File

@ -7,32 +7,38 @@ from pathlib import Path
from functools import partial
import json, download, asyncio
from typing import Optional
import os
app = FastAPI()
# app.mount("/static", StaticFiles(directory="/app/app/static"), name="static")
app.mount("/data", StaticFiles(directory="/data"), name="data")
templates = Jinja2Templates(directory="templates")
templates = Jinja2Templates(directory="app/templates")
loop = asyncio.get_running_loop()
# JSON cache
cached_data = None
# api
@app.get("/dropout/update")
async def dropoutUpdate():
global cached_data
try:
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."})
except Exception as e:
return JSONResponse(status_code=500, content={"status": "error", "message": str(e)})
@app.get("/dropout/series")
async def dropoutSeries():
file_path = Path("/data/dropout.json")
if file_path.exists():
with file_path.open("r", encoding="utf-8") as f:
data = json.load(f)
return JSONResponse(content=data)
global cached_data
if cached_data is None:
await dropoutUpdate()
return JSONResponse(content=cached_data)
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.")
@ -70,25 +76,29 @@ async def ydl(url: str, location: str):
except Exception as 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)
async def index(request: Request):
global cached_data
if cached_data is None:
with open('/data/dropout.json') as f:
cached_data = json.load(f)
return templates.TemplateResponse("index.html", {"request": request, "data": cached_data})
try:
if cached_data is None:
await dropoutUpdate()
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>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Show Posters</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.tile {
width: 400px;
margin: 10px;
text-align: center;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
}
.tile img {
width: 100%;
height: auto;
}
.tile .title {
padding: 10px;
background-color: #f4f4f4;
}
</style>
</head>
<body>
{% for item in data %}
<div class="tile">
<img src="{{ item['POSTER'] }}" alt="{{ item['SHOW'] }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Show Posters</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
flex-wrap: wrap;
justify-content: center;
background-color: #141414 !important;
}
.tile {
width: 400px;
margin: 10px;
text-align: center;
border: 1px solid #030000;
border-radius: 8px;
overflow: hidden;
box-shadow: #00000069 0px 5px 10px;
transition: all 0.2s ease;
position: relative;
}
.tile:hover {
transform: scale(1.05);
box-shadow: #00000069 0px 10px 15px;
}
.tile img {
width: 100%;
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="favorite-star">&#9733;</div>
</div>
{% endfor %}
</body>
{% endfor %}
<!-- <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>

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
uvicorn
jinja2
python-multipart
python-multipart
selenium