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,
|
||||
"justMyCode": true,
|
||||
"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 urllib.parse import urlsplit
|
||||
|
||||
@ -12,23 +14,10 @@ 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')
|
||||
|
||||
# Find the first <img> anywhere inside a .product-feature
|
||||
feature_section = soup.find(class_='product-feature')
|
||||
if not feature_section:
|
||||
return None
|
||||
|
||||
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']
|
||||
|
||||
def poster(url, name, save_dir='/data/posters/', force_download=False):
|
||||
# Use alt for filename if available, fallback to a generic name
|
||||
alt_value = img_tag.get('alt', 'image')
|
||||
path = urlsplit(img_url).path
|
||||
alt_value = name
|
||||
path = urlsplit(url).path
|
||||
ext = os.path.splitext(path)[-1] or '.jpeg'
|
||||
|
||||
safe_name = re.sub(r'[^a-zA-Z0-9\s]', '', alt_value).replace(' ', '_')
|
||||
@ -37,13 +26,11 @@ class grab():
|
||||
|
||||
if not os.path.exists(filepath) or force_download:
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
img_data = requests.get(img_url).content
|
||||
img_data = requests.get(url).content
|
||||
with open(filepath, 'wb') as handler:
|
||||
handler.write(img_data)
|
||||
|
||||
return filepath
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
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:
|
||||
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 = {}
|
||||
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)
|
||||
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():
|
||||
|
||||
56
app/main.py
56
app/main.py
@ -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
|
||||
try:
|
||||
if cached_data is None:
|
||||
with open('/data/dropout.json') as f:
|
||||
cached_data = json.load(f)
|
||||
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)})
|
||||
@ -10,31 +10,155 @@
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
background-color: #141414 !important;
|
||||
}
|
||||
|
||||
.tile {
|
||||
width: 400px;
|
||||
margin: 10px;
|
||||
text-align: center;
|
||||
border: 1px solid #ddd;
|
||||
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;
|
||||
background-color: #f4f4f4;
|
||||
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">
|
||||
<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">★</div>
|
||||
</div>
|
||||
{% 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
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>
|
||||
@ -6,3 +6,4 @@ pathlib
|
||||
uvicorn
|
||||
jinja2
|
||||
python-multipart
|
||||
selenium
|
||||
Loading…
x
Reference in New Issue
Block a user