localhub/movie_hub.py
2025-03-09 11:51:39 -04:00

362 lines
15 KiB
Python
Executable File

import cherrypy
import requests
import os
import math
import pycountry
import json
import re
import threading
import time
import urllib.request
import xml.etree.ElementTree as ET
from PyMovieDb import IMDB
import subprocess
import yt_dlp
from downloadScript import download
imdb = IMDB()
top_movies = []
recent_movies = []
priorities = [
{"type": "web", "video_codec": "x264"},
{"type": "web", "video_codec": "x265"},
{"type": "bluray", "video_codec": "x264"},
{"type": "bluray", "video_codec": "x265"},
]
def createMoviePoster(json_format):
movie_data = json_format
movie_readout = ""
plex_movies = json.load(open('/mnt/moviehub/plex_movies.json', 'r'))
for movie in movie_data:
res720 = {}
res1080 = {}
resOther = {}
availableRes = []
downButton = ""
plex_server_resolution = []
title = movie["title_english"]
rating = movie["rating"]
year = movie["year"]
imdb = movie["imdb_code"]
language = pycountry.languages.get(alpha_2=movie['language'])
language = language.name if language is not None else language
poster_url = movie["large_cover_image"]
for movie_match in plex_movies["movies"]:
if str(movie_match["video"]["title"].lower()) == str(movie["title"].lower()) and abs(int(movie_match["video"]["year"]) - year) <= 1:
plex_server_resolution.append(movie_match["media"]["videoResolution"]+"p")
for priority in priorities:
for resolution in movie["torrents"]:
if (resolution["quality"] == "720p" and
resolution["type"] == priority["type"] and
resolution["video_codec"] == priority["video_codec"]):
res720["res"] = resolution["quality"]
res720["url"] = resolution["url"]
break
for priority in priorities:
for resolution in movie["torrents"]:
if (resolution["quality"] == "1080p" and
resolution["type"] == priority["type"] and
resolution["video_codec"] == priority["video_codec"]):
res1080["res"] = resolution["quality"]
res1080["url"] = resolution["url"]
break
if len(res720) == 0 and len(res1080) == 0:
for resolution in movie["torrents"]:
resOther["res"] = resolution["quality"]
resOther["url"] = resolution["url"]
break
availableRes.append(res720)
availableRes.append(res1080)
availableRes.append(resOther)
if availableRes:
for resolution in availableRes:
res = resolution.get('res')
url = resolution.get('url')
if res is not None and url is not None:
extra_class = "resolution_exists" if res in plex_server_resolution else "download-link resolution_available"
downButton += f"""<a class="resolution {extra_class}" data-filename="{title} ({year}) ({res}).torrent" data-url="{url}">{res}</a>\n"""
# Create the movie readout HTML for each movie
movie_readout += f"""
<div class="movie">
<div class="details">
<b class="chip rating">{rating}<i class='bx bxs-star'></i></b>
<b class="chip">{year}</b>
<b class="chip">{language}</b>
</div>
<a href="https://www.imdb.com/title/{imdb}" target="_blank">
<img src="{poster_url}" onerror="this.src='images/large-cover.jpg'" alt="{title} Poster">
</a>
<div class="description">
<h4>{title}</h4>
</div>
<div class="resDownload">
{downButton}
</div>
</div>"""
return movie_readout
def navBar(json=None):
movieCount =''
movieCount = '<b id="matches">'+str(json['data']['movie_count'])+' Matches</b>' if json != None else ""
return open('html/navbar.html').read().format(movieCount=movieCount)
def pageButton(json, query_term):
pages = ''
movieCount = json['data']['movie_count']
resultLimit = json['data']['limit']
activePage = json['data']['page_number']
pageLimit = math.ceil(movieCount / resultLimit)
# Calculate the range of pages to display (5 pages)
start_page = max(1, activePage - 2)
end_page = min(pageLimit, start_page + 4)
if end_page - start_page < 4: # Ensure we always show 5 pages if possible
start_page = max(1, end_page - 4)
for number in range(start_page, end_page + 1):
if number == activePage:
pages += "<input type='hidden' id='activePage' data-page='" + str(number) + "'>"
pages += "<a class='pageNumber activePage' data-page='" + str(number) + "'>" + str(number) + "</a>"
else:
pages += "<a class='pageNumber' data-page='" + str(number) + "'>" + str(number) + "</a>"
if pageLimit == 1:
return ''
else:
# Load your HTML template from the file 'pagebutton.html' and format it with the generated pages.
return open('html/pagebutton.html').read().format(pages=pages, query_term=query_term, pageLimit=pageLimit)
def eztv_json():
return 'pass'
class fetch():
def imdb_data(query_term=None):
json_data = imdb.search(query_term, tv=True) if query_term is not None else imdb.popular_movies(genre=None, sort_by=None)
data = json.loads(json_data)
results_data = data.get('results', [])
return results_data
def yts_movies(parameter=None, movie=None):
movie_id = movie['id'] if movie is not None else ""
url = f'https://yts.mx/api/v2/list_movies.json{parameter}{movie_id}'
data = requests.get(url).json()
if 'data' in data and data['data'].get('movie_count', 0) > 0:
return data['data']['movies'][0] if movie is not None else data["data"]["movies"]
else:
return None
def plex_data():
url = f'http://10.0.0.105:32400/library/sections/1/all'
response = requests.get(url)
if response.status_code == 200:
plex_xml_data = response.text
root = ET.fromstring(plex_xml_data)
movies = []
for video_elem in root.findall('.//Video'):
video_keys = {}
media_keys = {}
for key, value in video_elem.items():
video_keys[key] = value
media_element = video_elem.find(".//Media")
if media_element is not None:
media_attributes = media_element.attrib
for key, value in media_attributes.items():
media_keys[key] = value
movie_info = {
'video': video_keys,
'media': media_keys
}
movies.append(movie_info)
movie_dict = {'movies': movies}
json_data = json.dumps(movie_dict, indent=2)
with open('/mnt/moviehub/plex_movies.json', 'w') as json_file:
json_file.write(json_data)
else:
print(f'Error: {response.status_code}')
class background():
def update_top_movies():
while True:
global top_movies # Access the global variable
# Your code to fetch and update the top 18 movies
new_top_movies = []
for movie in fetch.imdb_data():
if len(new_top_movies) >= 12:
break
movie_data = fetch.yts_movies('?query_term=', movie)
if movie_data is not None:
new_top_movies.append(movie_data)
top_movies = new_top_movies # Update the global variable
global recent_movies
recent_movies = fetch.yts_movies('?sort_by=date_added&limit=18')
fetch.plex_data()
time.sleep(3600)
background_thread = threading.Thread(target=background.update_top_movies)
background_thread.daemon = True # Set the thread as a daemon to exit when the main program exits
background_thread.start()
class MovieApp:
@cherrypy.expose
def index(self):
latestMoviesTiles = createMoviePoster(top_movies)
recentMoviesTiles = createMoviePoster(recent_movies)
return open('html/index.html').read().format(latestMoviesTiles=latestMoviesTiles,recentMoviesTiles=recentMoviesTiles,navBar=navBar())
@cherrypy.expose
def movie(self, query_term='', page='1', genre=None, minimum_rating=None):
api_url = f"https://yts.mx/api/v2/list_movies.json?limit=30&query_term={query_term}&page={page}"
api_url += f"&genre={genre}" if genre is not None else ""
api_url += f"&minimum_rating={minimum_rating}" if minimum_rating is not None else ""
response = requests.get(api_url)
if response.status_code == 200 and response.json()['status_message'] == "Query was successful" and response.json()['data']['movie_count'] > 0:
movie_tiles = createMoviePoster(response.json()["data"]["movies"])
pageBar = pageButton(response.json(), query_term)
else:
movie_tiles = "<h1>No matches found<h1>"
pageBar = ""
return open('html/movie.html').read().format(movie_tiles=movie_tiles,navBar=navBar(response.json()),pageButton=pageBar)
@cherrypy.expose
def tv(self, query_term=''):
poster_x, poster_y = '500', '750'
results_data = fetch.imdb_data(query_term)
html = ''
regex_pattern = r'_V1_QL\d+_([A-Z]+)(\d+)_CR(\d+),(\d+),(\d+),(\d+)_'
for result in results_data:
extract = re.search(regex_pattern, result['poster'])
item_id = result.get('id', '').replace('tt', '')
zoom = int(extract.group(4))
move_x = int(extract.group(3)) if extract else 0
move_x = move_x * 9 if move_x != 0 else move_x + 6.5
upscale_num = '510' if extract.group(2) == extract.group(5) else poster_y
resolution = f"""_V1_QL100_{extract.group(1)}{upscale_num}_CR{str(move_x)},{str(zoom)},{poster_x},{poster_y}_"""
poster_url = re.sub(r'_V1[\w_,]+', resolution, result['poster'])
title = result['name']
html += f"""<img style="width: 200px;" src="{poster_url}" id='{item_id}' onerror="this.src='images/large-cover.jpg'" alt="{title} Poster">"""
return html
@cherrypy.expose
def download(self, url, filename):
save_path = os.path.join('/mnt/data/deluge/watch/movies', filename)
title = filename.replace('.torrent','')
response = requests.get(url)
if response.status_code == 200:
with open(save_path, 'wb') as file:
file.write(response.content)
return(f"Download has been set for '{title}'.")
else:
print(f"Failed to download the file from '{url}'.")
return(f"Failed to send download request. Contact your system administrator.")
@cherrypy.expose
def anime(self):
rss = open('/mnt/deluge/config/yarss2.conf').read()
config = {} # Create an empty dictionary to store configuration
with open('/mnt/deluge/config/yarss2.conf', 'r') as file:
lines = file.readlines()
current_section = None
for line in lines:
line = line.strip()
if line.startswith("{"):
current_section = {}
elif line.startswith("}"):
if current_section:
config.update(current_section)
current_section = None
elif current_section:
key, value = line.split(":")
current_section[key.strip()] = value.strip()
# The 'config' dictionary now contains your configuration data
print(config)
return config
@cherrypy.expose
def downloader(self):
return open('html/ydl.html').read().format(navBar=navBar())
@cherrypy.expose
def dropout(self):
return open('html/dropout.html').read().format(navBar=navBar())
@cherrypy.expose
def dropoutDownloader(self,show,season,episode=None):
download.dropout(show,season,episode)
@cherrypy.expose
def ydl(self, url, location):
dl_ops = {'paths': {'temp': '/mnt/data/downloader/temp', 'home': location}, 'outtmpl': '%(uploader)s/%(title)s.%(ext)s'}
if location == "/mnt/plex/music/podcast":
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/music/asmr":
dl_ops['format'] = 'bestaudio/best[ext=mp3]'
dl_ops['postprocessors'] = [{
'key': 'FFmpegExtractAudio',
'preferredcodec': 'mp3',
'preferredquality': '192',
}, {
'key': 'FFmpegMetadata',
'add_metadata': True,
}]
else:
dl_ops['format'] = 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best'
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.")
@cherrypy.expose
def plex(self):
fetch.plex_data()
@cherrypy.expose
def yts(self):
background.update_top_movies()
@cherrypy.expose
def imdb(self):
return fetch.imdb_data()
cherrypy.config.update({
'tools.staticdir.on': True,
'tools.staticdir.dir': os.getcwd(),
'server.socket_host': '0.0.0.0',
'server.socket_port': 8008,
'favicon.ico': 'favicon.ico',
})
if __name__ == '__main__':
cherrypy.quickstart(MovieApp())