This commit is contained in:
TylerCG 2025-04-20 00:21:06 -04:00
parent ad995affd5
commit c5e6057dff
8 changed files with 708 additions and 1 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,22 @@
# Use official Python image
FROM python:3.11-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Set work directory
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy project files
COPY . .
# Expose port
EXPOSE 8000
# Run the app with Uvicorn
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

BIN
app/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

531
app/data/dropout.json Normal file
View File

@ -0,0 +1,531 @@
[
{
"SHOW": "Adventuring Academy",
"URL": "https://www.dropout.tv/adventuring-academy",
"SEASONS": [
"1",
"2",
"3",
"4",
"5"
]
},
{
"SHOW": "Bad Internet",
"URL": "https://www.dropout.tv/bad-internet",
"SEASONS": [
"1"
]
},
{
"SHOW": "Breaking News No Laugh Newsroom",
"URL": "https://www.dropout.tv/breaking-news-no-laugh-newsroom",
"SEASONS": [
"1",
"2",
"3",
"4",
"5",
"6",
"7"
]
},
{
"SHOW": "Cartoon Hell",
"URL": "https://www.dropout.tv/cartoon-hell",
"SEASONS": [
"1",
"2"
]
},
{
"SHOW": "Dimension 20",
"URL": "https://www.dropout.tv/dimension-20",
"SEASONS": [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25"
]
},
{
"SHOW": "Dimension 20 A Court of Fey Flowers",
"URL": "https://www.dropout.tv/dimension-20-a-court-of-fey-flowers",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 A Crown of Candy",
"URL": "https://www.dropout.tv/dimension-20-a-crown-of-candy",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 A Starstruck Odyssey",
"URL": "https://www.dropout.tv/dimension-20-a-starstruck-odyssey",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 Animated",
"URL": "https://www.dropout.tv/dimension-20-animated",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 Audio Only",
"URL": "https://www.dropout.tv/dimension-20-audio-only",
"SEASONS": [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25"
]
},
{
"SHOW": "Dimension 20 Burrow's End",
"URL": "https://www.dropout.tv/dimension-20-burrow-s-end",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 Coffin Run",
"URL": "https://www.dropout.tv/dimension-20-coffin-run",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 Dungeons And Drag Queens",
"URL": "https://www.dropout.tv/dimension-20-dungeons-and-drag-queens",
"SEASONS": [
"1",
"2"
]
},
{
"SHOW": "Dimension 20 Escape From The Bloodkeep",
"URL": "https://www.dropout.tv/dimension-20-escape-from-the-bloodkeep",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 Fantasy High",
"URL": "https://www.dropout.tv/dimension-20-fantasy-high",
"SEASONS": [
"1",
"2",
"3"
]
},
{
"SHOW": "Dimension 20 Fantasy High Junior Year",
"URL": "https://www.dropout.tv/dimension-20-fantasy-high-junior-year",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 Leviathan",
"URL": "https://www.dropout.tv/dimension-20-leviathan",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 Mentopolis",
"URL": "https://www.dropout.tv/dimension-20-mentopolis",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 Misfits And Magic",
"URL": "https://www.dropout.tv/dimension-20-misfits-and-magic",
"SEASONS": [
"1",
"2",
"3"
]
},
{
"SHOW": "Dimension 20 Never Stop Blowing Up",
"URL": "https://www.dropout.tv/dimension-20-never-stop-blowing-up",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 Neverafter",
"URL": "https://www.dropout.tv/dimension-20-neverafter",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 Shriek Week",
"URL": "https://www.dropout.tv/dimension-20-shriek-week",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 The Ravening War",
"URL": "https://www.dropout.tv/dimension-20-the-ravening-war",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 The Seven",
"URL": "https://www.dropout.tv/dimension-20-the-seven",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 The Unsleeping City",
"URL": "https://www.dropout.tv/dimension-20-the-unsleeping-city",
"SEASONS": [
"1",
"2"
]
},
{
"SHOW": "Dimension 20 Time Quangle",
"URL": "https://www.dropout.tv/dimension-20-time-quangle",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 Tiny Heist",
"URL": "https://www.dropout.tv/dimension-20-tiny-heist",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20 Titan Takedown",
"URL": "https://www.dropout.tv/dimension-20-titan-takedown",
"SEASONS": [
"1"
]
},
{
"SHOW": "Dimension 20's Adventuring Party",
"URL": "https://www.dropout.tv/dimension-20-s-adventuring-party",
"SEASONS": [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20"
]
},
{
"SHOW": "Dirty Laundry",
"URL": "https://www.dropout.tv/dirty-laundry",
"SEASONS": [
"1",
"2",
"3",
"4",
"69"
]
},
{
"SHOW": "Dropout Music",
"URL": "https://www.dropout.tv/dropout-music",
"SEASONS": [
"1"
]
},
{
"SHOW": "Erotic Clubhouse",
"URL": "https://www.dropout.tv/erotic-clubhouse",
"SEASONS": [
"1"
]
},
{
"SHOW": "Fantasy High Extra Credit",
"URL": "https://www.dropout.tv/fantasy-high-extra-credit",
"SEASONS": [
"1"
]
},
{
"SHOW": "Game Changer",
"URL": "https://www.dropout.tv/game-changer",
"SEASONS": [
"1",
"2",
"3",
"4",
"5",
"6",
"7"
]
},
{
"SHOW": "Game Changer Animated",
"URL": "https://www.dropout.tv/game-changer-animated",
"SEASONS": [
"1"
]
},
{
"SHOW": "Game Changer Battle Royale",
"URL": "https://www.dropout.tv/game-changer-battle-royale",
"SEASONS": [
"1"
]
},
{
"SHOW": "Gastronauts",
"URL": "https://www.dropout.tv/gastronauts",
"SEASONS": [
"1"
]
},
{
"SHOW": "Gods of Food",
"URL": "https://www.dropout.tv/gods-of-food",
"SEASONS": [
"1"
]
},
{
"SHOW": "Kingpin Katie",
"URL": "https://www.dropout.tv/kingpin-katie",
"SEASONS": [
"1"
]
},
{
"SHOW": "Kingpin Katie The Web Series",
"URL": "https://www.dropout.tv/kingpin-katie-the-web-series",
"SEASONS": [
"1"
]
},
{
"SHOW": "Make Some Noise",
"URL": "https://www.dropout.tv/make-some-noise",
"SEASONS": [
"1",
"2",
"3"
]
},
{
"SHOW": "Mice Murder",
"URL": "https://www.dropout.tv/mice-murder",
"SEASONS": [
"1"
]
},
{
"SHOW": "Monet's Slumber Party",
"URL": "https://www.dropout.tv/monet-s-slumber-party",
"SEASONS": [
"1"
]
},
{
"SHOW": "Nobody Asked",
"URL": "https://www.dropout.tv/nobody-asked",
"SEASONS": [
"1"
]
},
{
"SHOW": "Paranoia",
"URL": "https://www.dropout.tv/paranoia",
"SEASONS": [
"1",
"2"
]
},
{
"SHOW": "Parlor Room",
"URL": "https://www.dropout.tv/parlor-room",
"SEASONS": [
"1"
]
},
{
"SHOW": "Play It By Ear",
"URL": "https://www.dropout.tv/play-it-by-ear",
"SEASONS": [
"1",
"2"
]
},
{
"SHOW": "See Plum Run",
"URL": "https://www.dropout.tv/see-plum-run",
"SEASONS": [
"1"
]
},
{
"SHOW": "Smartypants",
"URL": "https://www.dropout.tv/smartypants",
"SEASONS": [
"1",
"2"
]
},
{
"SHOW": "Thousandaires",
"URL": "https://www.dropout.tv/thousandaires",
"SEASONS": [
"1"
]
},
{
"SHOW": "Total Forgiveness",
"URL": "https://www.dropout.tv/total-forgiveness",
"SEASONS": [
"1"
]
},
{
"SHOW": "Total Forgiveness Early Payoff",
"URL": "https://www.dropout.tv/total-forgiveness-early-payoff",
"SEASONS": [
"1"
]
},
{
"SHOW": "Troopers 1",
"URL": "https://www.dropout.tv/troopers-1",
"SEASONS": [
"1"
]
},
{
"SHOW": "Troopers Animated",
"URL": "https://www.dropout.tv/troopers-animated",
"SEASONS": [
"1"
]
},
{
"SHOW": "Ultramechatron Team Go 1",
"URL": "https://www.dropout.tv/ultramechatron-team-go-1",
"SEASONS": [
"1"
]
},
{
"SHOW": "Um Actually",
"URL": "https://www.dropout.tv/um-actually",
"SEASONS": [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10"
]
},
{
"SHOW": "Very Important People",
"URL": "https://www.dropout.tv/very-important-people",
"SEASONS": [
"1",
"2"
]
},
{
"SHOW": "What The Facts 101",
"URL": "https://www.dropout.tv/what-the-facts-101",
"SEASONS": [
"1"
]
},
{
"SHOW": "Where In The Eff Is Sarah Cincinnati",
"URL": "https://www.dropout.tv/where-in-the-eff-is-sarah-cincinnati",
"SEASONS": [
"1"
]
},
{
"SHOW": "Wtf 101",
"URL": "https://www.dropout.tv/wtf-101",
"SEASONS": [
"1"
]
}
]

131
app/download.py Normal file
View File

@ -0,0 +1,131 @@
import os, yt_dlp, json, requests
from bs4 import BeautifulSoup
class grab():
def season(url):
page_html=requests.get(url)
soup = BeautifulSoup(page_html.text, 'html.parser')
select_element = soup.find('select', class_='js-switch-season')
options = select_element.find_all('option')
option_values = [option['value'] for option in options if option.has_attr('value')]
seasons = [item.replace(url+'/season:', '') for item in option_values]
return seasons
class dropout():
def show(show,season,episode):
directory='/mnt/plex/tv/'+show+'/Season '+season+'/'
with open('data/dropout.json', 'r') as json_file:
url_mapping = json.load(json_file)
url = next((item['URL'] for item in url_mapping if item['SHOW'] == show), None)
if url is not None:
url = f'{url}/season:{season}'
else:
raise ValueError(f"Show '{show}' not found in the JSON data.")
if not os.path.exists(directory):
os.makedirs(directory)
if episode is None or episode == '':
episode = '%(playlist_index)02d'
else:
try:
dl_ops['playliststart'] = int(episode)
except ValueError:
# Handle the error, e.g., log it or set a default value
dl_ops['playliststart'] = 0 # or some appropriate default value
dl_ops = {
'format': 'bestvideo+bestaudio/best',
'audio_quality': '256K',
'paths': {
'temp': '/mnt/data/downloader/temp',
'home': directory
},
'cookiefile': 'cookies.txt',
'reject_title': [
r'(?i).*behind.?the.?scenes.*', # Reject titles with "behind the scenes" (case-insensitive)
r'(?i).*trailer.*', # Reject titles with "trailer" (case-insensitive)
r'(?i).*recap.*', # Reject titles with "recap" (case-insensitive)
r'(?i).*last.looks.*' # Reject titles with "last looks" (case-insensitive)
],
'outtmpl': show + ' - S'+f"{int(season):02}"+'E'+episode+' - %(title)s.%(ext)s',
'noplaylist': True,
# Additional options for downloading subtitles
'writesubtitles': True, # Download subtitles
'subtitleslangs': ['en'] # Specify the language for subtitles (e.g., 'en' for English)
}
with yt_dlp.YoutubeDL(dl_ops) as ydl:
ydl.download([url])
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]
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['SEASONS'] = grab.season(url)
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:
json.dump(sorted_json_data, json_file, indent=4)
return sorted_json_data
def ydl(url, location):
dl_ops = {'paths': {'temp': '/mnt/data/downloader/temp', 'home': location}, 'outtmpl': '%(uploader)s/%(title)s.%(ext)s'}
if location == "/mnt/plex/podcasts":
dl_ops['format'] = 'bestaudio/best[ext=mp3]'
dl_ops['postprocessors'] = [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192',
}, {
'key': 'FFmpegMetadata',
'add_metadata': True,
}]
elif location == "/mnt/plex/asmr":
dl_ops['format'] = 'bestaudio/best[ext=mp3]'
dl_ops['postprocessors'] = [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192',
}, {
'key': 'FFmpegMetadata',
'add_metadata': True,
}]
elif location == "/mnt/nsfw":
dl_ops['format'] = 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best'
else:
dl_ops = {
'format': 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best',
'cookiefile': 'yt-cookies.txt'
}
with yt_dlp.YoutubeDL(dl_ops) as ydl:
ydl.download(url)
# Extracting video information
video_info = ydl.extract_info(url, download=False)
thumbnail_url = video_info.get('thumbnail')
# Download the thumbnail image
if thumbnail_url:
try:
thumbnail_filename = os.path.join(location, f"{video_info['id']}.jpg")
with open(thumbnail_filename, 'wb') as thumbnail_file:
thumbnail_file.write(requests.get(thumbnail_url).content)
print("Downloaded MP4 and downloaded thumbnail successfully!")
except Exception as e:
print(f"Error downloading thumbnail: {str(e)}")
else:
print("Downloaded MP4 but no thumbnail found.")

View File

@ -1,7 +1,10 @@
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from pathlib import Path
import download
import json
app = FastAPI() app = FastAPI()
@ -11,6 +14,26 @@ app.mount("/static", StaticFiles(directory="static"), name="static")
# Jinja2 template directory # Jinja2 template directory
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
# api
@app.get("/dropoutUpdate")
async def grab():
return download.dropout.series()
@app.get("/dropoutSeries", response_class=JSONResponse)
async def read():
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)
return JSONResponse(content={"error": "File not found"}, status_code=404)
@app.get("/dropoutDownload")
async def download(show: str, season: str, episode: str):
download.dropout.show(show,season,episode)
# html
@app.get("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse)
async def read_root(request: Request): async def read_root(request: Request):
return templates.TemplateResponse("index.html", {"request": request, "message": "Hello from FastAPI + Jinja2!"}) return templates.TemplateResponse("index.html", {"request": request, "message": "Hello from FastAPI + Jinja2!"})