bhdupload/bhdupload.py
pwgen b1a70c2145 fix: recursively check all returned pages for correct movie
fixes assumption of getting the first result back especially for items that are very generic. Such as 2012
added new verbose mode and cleaned up some of the output
2021-10-23 23:23:50 +00:00

422 lines
13 KiB
Python

#!/usr/bin/env python
import argparse
import atexit
import guessit
import json
import math
import requests
import os
import subprocess
import sys
import tempfile
from dotenv import load_dotenv
def absoluteFilePaths(directory):
""" Get full file paths within a specific directory
Keyword arguments:
directory -- directory path containing files
"""
files = []
for dirpath, _, filenames in os.walk(directory):
for f in sorted(filenames):
files.append(os.path.abspath(os.path.join(dirpath, f)))
return files
def readFiles(files, key='image[]'):
""" Open files for Requests payload
Keyword arguments:
files -- list of files (full paths)
key -- Requests payload form field name
"""
result = []
for f in files:
result.append((key, (os.path.basename(f), open(f, 'rb'))))
return result
def getArgs():
""" Get command-line arguments
"""
parser = argparse.ArgumentParser()
parser.add_argument('input', action='store', type=str, help='Input file')
parser.add_argument('-m', dest='screenshot', action='store', type=int, default=1200, help='Time to screenshot')
parser.add_argument('-c', dest='count', action='store', type=int, default=4, help='Amount of screenshots to take')
parser.add_argument('-u', dest='images', action='store_false', default=True,
help='Do not Create & Upload Screenshots')
parser.add_argument('-t', dest='torrent', action='store_false', default=True,
help='Do not Create Torrent File')
parser.add_argument('-l', dest='live', action='store_true', help='Upload instead of going to drafts')
parser.add_argument('-a', dest='anon', action='store_true', help='Set an Anonymous upload')
parser.add_argument('-v', dest='debug', action='store_true', help='Enable extra debug mode')
return parser.parse_args()
def getDuration(file):
""" Get duration of video file (in seconds)
Keyword arguments:
file -- input file (full path)
"""
p = subprocess.Popen([
'ffprobe',
'-v', 'error',
'-loglevel', '0',
'-show_entries', 'format=duration',
'-of', 'default=noprint_wrappers=1:nokey=1',
file
], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
duration = p.stdout.read().decode('utf-8')
retval = p.wait()
if retval == 127:
raise ValueError('ffprobe is not installed.')
return int(math.floor(float(duration)))
def getPieceSize(file):
""" Calculates a respectible piece size
"""
size = os.path.getsize(file)
mb = size/1024/1024
if mb > 72000:
piece = 23
if mb < 72000:
piece = 22
if mb < 16000:
piece = 21
if mb < 8000:
piece = 20
return piece
def mkScreenshot(file, outputDir, offset):
""" Make a screenshot from file in specified output dir, with the specified offset
Keyword arguments:
file -- input file (full path)
outputDir -- directory to write screenshot to
offset -- how many seconds into the file to pull the screenshot from
"""
p = subprocess.Popen([
'ffmpeg',
'-ss', str(offset),
'-r', '1',
'-i', file,
outputDir + '/ss' + str(offset).zfill(5) + '.png'
], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
retval = p.wait()
if retval == 127:
raise ValueError('ffmpeg is not installed.')
return True
def mkScreenshots(file, count, tempdir, interval):
""" Make screenshots from file, with the specified interval between screenshots
Keyword arguments:
file -- input file (full path)
count -- amount of screenshots to take
tempdir -- our temporary directory
interval -- interval between screenshots (seconds)
"""
for i in range(1, count + 1):
sys.stdout.write(str(i) + '.. ')
sys.stdout.flush()
mkScreenshot(file, tempdir, i * interval)
print('Done!')
return absoluteFilePaths(tempdir)
def uploadScreenshots(files):
""" Upload screenshots to Imgbox
Keyword arguments:
files - list of images (full paths)
"""
newargs = ['imgbox']
for img in files:
newargs.append(img)
newargs.append("-w")
newargs.append("350")
newargs.append("-j")
# print(f"Running: {newargs} to create screenshots")
p = subprocess.run(newargs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if p.check_returncode():
print(f"Possible issue uploading img: {p.stderr}")
return p.stdout
def removeDir(directory):
""" Removes a directory including all files in that directory
Keyword arguments:
directory -- directory path containing files
"""
files = absoluteFilePaths(directory)
for f in files:
os.remove(f)
os.rmdir(directory)
def mktorrent(passkey, directory, tempdir):
piecelength = getPieceSize(directory)
base = f"mktorrent -p -l {piecelength} -a https://beyond-hd.me/announce/{passkey}"
output = f"{tempdir}/torrent.torrent"
args = base.split()
args.append("-o")
args.append(output)
args.append(directory)
p = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if p.check_returncode():
print(f"{directory} failed to create torrent file: {output}")
return "failure"
return f"{output}"
def mediainfo(directory, tempdir):
args = ["mediainfo", f"{directory}"]
mediafile = open(f"{tempdir}/mediainfo.txt", 'w')
p = subprocess.run(args, stdout=mediafile)
if p.check_returncode():
print(f"{directory} failed to create mediainfo file")
mediafile.flush()
return "failure"
mediafile.flush()
return f"{tempdir}/mediainfo.txt"
def find_info(name, page=1):
torrent_name = str(name)
print(f"Searching for {torrent_name}...")
# first search on name
if page == 1:
r = SESS.post(BHD_SEARCH, data={'action': 'search', 'search': torrent_name})
else:
r = SESS.post(BHD_SEARCH, data={'action': 'search', 'search': torrent_name, 'page': page})
try:
if r.status_code == 200:
json = r.json()
if json["total_results"] >= 1:
if args.debug:
print(f"Total Results: {json['total_results']}")
print(f"Current Page: {page} of {json['total_pages']}")
for item in json["results"]:
guess = dict(guessit.guessit(item['name']))
title = (guess['title'])
if args.debug:
print(f"Current Item: Title: {title} - {torrent_name}")
if title == torrent_name:
# We found an exact match
imdb = item["imdb_id"]
tmdb = item["tmdb_id"]
category = item["category"]
pack = item["tv_pack"]
print(f"Found: {torrent_name} IMDB: {imdb}, TMDB: {tmdb}, Category: {category}, TV?: {pack} ")
return imdb, tmdb, category, pack
# If we get to the end of a page we need to check there are not more pages
while page != json["total_pages"]:
next_page = page + 1
imdb, tmdb, category, pack = find_info(name, next_page)
return imdb, tmdb, category, pack
# If we get to here we've never found {torrent_name}
print(f"Failed to find {torrent_name}")
return False, False, False, False
return imdb, tmdb, category, pack
except Exception as e:
print(f"Error finding torrent information: {e}")
print(f"{json}")
return "", "", "", ""
def find_gaps(name, _source, _reso=""):
'''
payload = {'name': name,
'description': BBCODE,
'category_id': category,
'type': reso,
'source': source,
'imdb_id': imdbid,
'tmdb_id': tmdbid,
'pack': pack,
'live': live,
'anon': anon}
'''
torrent_name = str(name)
source = str(_source)
reso = str(_reso)
action = ""
print(f"Searching for {torrent_name} - {source} - {reso}...")
# first search on name
r = SESS.post(BHD_SEARCH, data={
'action': 'search',
'search': torrent_name,
'source': source,
'types': reso})
try:
if r.status_code == 200:
_json = r.json()
if _json['total_results'] >= 1:
action = False
print(f"Found {_json['total_results']} matching our criteria...")
for result in _json['results']:
if result['seeders'] <= 1:
print(f"{result['name']} - Could be dead")
action = True
elif result['seeders'] >= 1 and result['seeders'] <= 3:
print(f"{result['name']} - Needs saving, not uploading")
action = False
else:
# No torrents match our Source & Resolution, We can upload safely!
print(f"Couldn't find torrents matching: {torrent_name}.{source}.{reso}")
action = True
return action
except Exception as e:
print(f"Error finding torrent information: {e}")
print(f"{_json}")
return False
# MAIN
# initialize variables
load_dotenv()
BHD_PASSKEY = os.getenv("BHD_PASSKEY")
BHD_API = os.getenv("BHD_API")
BHD_SEARCH = f'https://beyond-hd.me/api/torrents/{BHD_API}'
BHD_UPLOAD = f'https://beyond-hd.me/api/upload/{BHD_API}'
SESS = requests.session()
BBCODE = ""
tempdir = tempfile.mkdtemp()
atexit.register(removeDir, tempdir)
args = getArgs()
directory = args.input
if args.images:
# Get duration of input file
duration = getDuration(args.input)
# count number of screenshots to generate
count = int(args.count)
interval = args.screenshot/count
print(f'We are going to generate {count} @ {interval}s')
# Generate screenshots
print('Generating screenshots...')
screenshots = mkScreenshots(args.input, count, tempdir, interval)
if len(screenshots) == 0:
raise ValueError('No screenshots generated. Quitting!')
print(str(len(screenshots)) + ' screenshots generated.')
# Upload screenshots
print('Uploading screenshots...')
uploads = uploadScreenshots(screenshots)
# Check result for successful uploads
BBCODE = str()
uploaded = json.loads(uploads)
for up in uploaded:
if up["success"] is not True:
continue
url = up["web_url"]
img = up["thumbnail_url"]
BBCODE += f"[URL={url}][IMG]{img}[/IMG][/URL]"
BBCODE += "\n"
# Output results
print("BBCode:\n")
print(BBCODE)
if args.torrent:
file_name = os.path.basename(args.input)
print("Guessing information...")
# USE GUESSIT TO DEFINE MORE VARIABLES
guess = dict(guessit.guessit(args.input))
searchterm = (guess['title'])
reso = guess.get('screen_size', None)
source = (guess['source'])
other = guess.get('other', None)
lastchar = file_name[len(file_name)-4:]
if lastchar == ".mkv" or lastchar == ".avi" or lastchar == ".mp4":
name = str(file_name.replace(".", " ")[:-4])
else:
name = str(file_name.replace(".", " "))
# JANKY IF STATEMENTS TO MAKE GUESSIT OUTPUT API FRIENDLY
if "Web" in source:
source = "WEB"
elif ((source == "Blu-ray") and (other == "Remux") and (reso == "1080p")):
reso = "BD Remux"
elif "DVD" in name:
reso = "DVD Remux"
elif ((source == "Ultra HD Blu-ray") and (other == "Remux") and (reso == "2160p")):
reso = "UHD Remux"
source = "Blu-ray"
# Find the required info before we upload (hopefully BHD already has it uploaded)
imdbid, tmdbid, category, pack = find_info(searchterm)
live = 0
anon = 0
if args.live:
live = 1
if args.anon:
anon = 1
if category == "TV":
category = 2
if category == "Movies":
category = 1
payload = {'name': name,
'description': BBCODE,
'category_id': category,
'type': reso,
'source': source,
'imdb_id': imdbid,
'tmdb_id': tmdbid,
'pack': pack,
'live': live,
'anon': anon}
# Notify if it failed to be found
if imdbid is False or tmdbid is False or category is False:
print(f"Failed to find {searchterm} already on BHD - You might be the first!",
"You will need to edit it in drafts before uploading")
if live == 1:
print(f"We are sending {searchterm} to drafts instead of live. Fix it up!")
live = 0
# Generate MediaInfo
print("Creating mediainfo...")
mediainfo_file = mediainfo(args.input, tempdir)
# Generate TorrentFile
print("Creating torrent...")
torrent_file = mktorrent(BHD_PASSKEY, args.input, tempdir)
if args.debug:
print("")
print(f"Debug: We are about to submit the following: {payload}")
print("")
torrentfile = f"{torrent_file}"
mediainfofile = f"{mediainfo_file}"
files = {'file': open(torrentfile, 'rb'), 'mediainfo': open(mediainfofile, 'rb')}
response = requests.request("POST", BHD_UPLOAD, data=payload, files=files)
print("")
print(response.text)
# Cleanup
print('Done!')