Add YouTube downloader
This commit is contained in:
parent
dc8bb46734
commit
28ea47c961
6 changed files with 94 additions and 19 deletions
23
handler.py
23
handler.py
|
@ -3,11 +3,13 @@
|
||||||
import re
|
import re
|
||||||
import importlib
|
import importlib
|
||||||
import argparse
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
|
||||||
regs = {
|
regs = {
|
||||||
"\d/\d/\d_\d\.jpg": "krpanosteal",
|
"\d/\d/\d_\d\.jpg": "krpanosteal",
|
||||||
"pano\_[frblud].jpg": "krpanosteal",
|
"pano\_[frblud].jpg": "krpanosteal",
|
||||||
"my.matterport.com/show/": "matterportsteal"
|
"my.matterport.com/show/": "matterportsteal",
|
||||||
|
"youtube.com": "youtubesteal"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,18 +31,27 @@ def parse_url(url):
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('url', help='URL to process')
|
parser.add_argument('url', help='URL to process')
|
||||||
parser.add_argument('--title', help='title to be used for the file name')
|
parser.add_argument('--title', help='title to be used for the file name', default="No Title")
|
||||||
parser.add_argument("--rotation", nargs=3, type=int, help="rotation on x/y/z axes", metavar=("x","y","z"))
|
parser.add_argument("--rotation", nargs=3, type=int, help="rotation on x/y/z axes", metavar=("x","y","z"))
|
||||||
parser.add_argument("--resolution", type=int, nargs=2, metavar=("w","h"))
|
parser.add_argument("--resolution", type=int, nargs=2, metavar=("w","h"))
|
||||||
parser.add_argument("--output")
|
parser.add_argument("--output", default=".")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handler = parse_url(args.url)
|
handler = parse_url(args.url)
|
||||||
image = handler(args.url, args.rotation or [0,0,0], args.resolution or [3840, 1920])
|
image = handler(args.url, args.rotation or [0,0,0], args.resolution or [3840, 1920])
|
||||||
|
if not hasattr(image, "im"):
|
||||||
image.save(args.output + "/" + args.title + ".png")
|
with open(args.output + "/" + args.title + ".mkv", "wb") as video:
|
||||||
|
video.write(image)
|
||||||
|
subprocess.run(["/usr/bin/ffmpeg",
|
||||||
|
"-i", args.output + "/" + args.title + ".mkv",
|
||||||
|
"-ss", "00:00:10",
|
||||||
|
"-vframes", "1",
|
||||||
|
"-f", "image2",
|
||||||
|
args.output + "/" + args.title + ".thumb.jpg"])
|
||||||
|
else:
|
||||||
|
image.save(args.output + "/" + args.title + ".png")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
with open(args.output + "/" + args.title + ".err", "w") as errorfile:
|
with open(args.output + "/" + args.title + ".err", "w") as errorfile:
|
||||||
errorfile.write("")
|
errorfile.write(str(e))
|
||||||
|
|
2
run.sh
2
run.sh
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
mkdir /tmp/panosteal/ -p
|
mkdir /tmp/panosteal/youtube -p
|
||||||
gunicorn -c gunicorn.cfg server.daemon
|
gunicorn -c gunicorn.cfg server.daemon
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ JSON = "application/json"
|
||||||
XML = "text/xml"
|
XML = "text/xml"
|
||||||
TEXT = "text/plain"
|
TEXT = "text/plain"
|
||||||
PNG = "image/png"
|
PNG = "image/png"
|
||||||
|
MKV = "video/mkv"
|
||||||
|
JPG = "image/jpeg"
|
||||||
|
|
||||||
def static(req):
|
def static(req):
|
||||||
try:
|
try:
|
||||||
|
@ -92,15 +94,15 @@ def addjob(req):
|
||||||
return Response(status, ctype, content)
|
return Response(status, ctype, content)
|
||||||
|
|
||||||
def getjob(req):
|
def getjob(req):
|
||||||
jobid = req.path[-1]
|
jobid = req.path[-1].rstrip("-thumb").rstrip("-info")
|
||||||
content_disposition = None
|
content_disposition = None
|
||||||
|
|
||||||
found = glob.glob("/tmp/panosteal/%s---*.png" % jobid)
|
found = (glob.glob("/tmp/panosteal/%s---*.png" % jobid) + glob.glob("/tmp/panosteal/%s---*.mkv" % jobid)) if not req.path[-1].endswith("thumb") else glob.glob("/tmp/panosteal/%s---*.jpg" % jobid)
|
||||||
|
|
||||||
if found:
|
if found:
|
||||||
md5 = "Not happening."
|
md5 = "Not happening."
|
||||||
while True:
|
while True:
|
||||||
content = open(found[0], "rb").read()
|
content = open(found[0], "rb").read() if not req.path[-1].endswith("info") else b""
|
||||||
newmd5 = hashlib.md5(content).hexdigest()
|
newmd5 = hashlib.md5(content).hexdigest()
|
||||||
if newmd5 == md5:
|
if newmd5 == md5:
|
||||||
break
|
break
|
||||||
|
@ -108,8 +110,8 @@ def getjob(req):
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
code = HTTP200
|
code = HTTP200
|
||||||
ctype = PNG
|
ctype = PNG if found[0].endswith(".png") else JPG if found[0].endswith(".jpg") else MKV
|
||||||
content_disposition = found[0].split("---")[-1]
|
content_disposition = found[0].split("---")[-1] if not req.path[-1].endswith("info") else None
|
||||||
|
|
||||||
elif glob.glob("/tmp/panosteal/%s*err" % jobid):
|
elif glob.glob("/tmp/panosteal/%s*err" % jobid):
|
||||||
content = "<h1>500 Internal Server Error</h1>".encode()
|
content = "<h1>500 Internal Server Error</h1>".encode()
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<form class="" id="theform">
|
<form class="" id="theform">
|
||||||
<div class="form-group"> <label>URL</label> <input type="text" class="form-control" placeholder="https://example.com/0/0/0_0.jpg" name="url" required="required"> <small class="form-text text-muted">URL of an image contained in a krpano panorama or of a website containing a Matterport panorama</small> </div>
|
<div class="form-group"> <label>URL</label> <input type="text" class="form-control" placeholder="https://example.com/0/0/0_0.jpg" name="url" required="required">
|
||||||
<div class="form-group"> <label>Title</label> <input type="" class="form-control" placeholder="1234 - Shiny Place" id="title" name="title"> </div>
|
<div class="form-group"> <label>Title</label> <input type="" class="form-control" placeholder="1234 - Shiny Place" id="title" name="title"> </div>
|
||||||
<div class="form-group"> <label style="display: block;">Resolution</label> <input type="" class="form-control" placeholder="3840" name="width" style="width: 100px; display: inline;"> x <input type="" class="form-control" placeholder="1920" name="height" style="width: 100px; display: inline;"> </div>
|
<div class="form-group"> <label style="display: block;">Resolution</label> <input type="" class="form-control" placeholder="3840" name="width" style="width: 100px; display: inline;"> x <input type="" class="form-control" placeholder="1920" name="height" style="width: 100px; display: inline;"> </div>
|
||||||
<div id="options">
|
<div id="options">
|
||||||
|
|
|
@ -37,11 +37,23 @@ function failcard(jobid, title) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function finishcard(jobid, title) {
|
function finishcard(jobid, title, video) {
|
||||||
deletecard(jobid);
|
deletecard(jobid);
|
||||||
|
if (!video) {
|
||||||
var text = '<div class="col-sm-3" id="' + jobid + '"> <div class="card"> <img class="card-img-top img-fluid" src="/getjob/' + jobid + '" alt="Final Image"><div style="text-align: center; font-weight: bold;" class="card-block">' + title + '</div> <div style="text-align: center; color: white;" class="card-block"> <a href="/getjob/' + jobid + '" class="btn btn-primary">Download</a> <a onclick="deletecard(\'' + jobid + '\');" class="btn btn-danger">Hide</a></div> </div> </div>';
|
var text = '<div class="col-sm-3" id="' + jobid + '"> <div class="card"> <img class="card-img-top img-fluid" src="/getjob/' + jobid + '" alt="Final Image"><div style="text-align: center; font-weight: bold;" class="card-block">' + title + '</div> <div style="text-align: center; color: white;" class="card-block"> <a href="/getjob/' + jobid + '" class="btn btn-primary">Download</a> <a onclick="deletecard(\'' + jobid + '\');" class="btn btn-danger">Hide</a></div> </div> </div>';
|
||||||
|
} else {
|
||||||
|
var text = '<div class="col-sm-3" id="' + jobid + '"> <div class="card"> <img class="card-img-top img-fluid" id="' + jobid + '-thumb" src="/getjob/' + jobid + '-thumb" alt="Final Video"><div style="text-align: center; font-weight: bold;" class="card-block">' + title + '</div> <div style="text-align: center; color: white;" class="card-block"> <a href="/getjob/' + jobid + '" class="btn btn-primary">Download</a> <a onclick="deletecard(\'' + jobid + '\');" class="btn btn-danger">Hide</a></div> </div> </div>';
|
||||||
|
};
|
||||||
$('#cards').append(text);
|
$('#cards').append(text);
|
||||||
|
|
||||||
|
var counter = 0;
|
||||||
|
var interval = setInterval(function() {
|
||||||
|
var image = document.getElementById(jobid + '-thumb');
|
||||||
|
image.src = "/getjob/" + jobid + "-thumb?rand=" + Math.random();
|
||||||
|
if (++counter === 10) {
|
||||||
|
window.clearInterval(interval);
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#theform').submit(function(event){
|
$('#theform').submit(function(event){
|
||||||
|
@ -64,16 +76,16 @@ $('#theform').submit(function(event){
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
cache: false,
|
cache: false,
|
||||||
url: "/getjob/" + jobid,
|
url: "/getjob/" + jobid + "-info",
|
||||||
statusCode: {
|
statusCode: {
|
||||||
404: function() {
|
404: function() {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
failcard(jobid, title);
|
failcard(jobid, title);
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
200: function() {
|
200: function(data, tstatus, xhr) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
finishcard(jobid, title);
|
finishcard(jobid, title, (xhr.getResponseHeader('content-type') == "image/png") ? false : true);
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
500: function() {
|
500: function() {
|
||||||
|
|
50
youtubesteal/__init__.py
Normal file
50
youtubesteal/__init__.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import PIL.Image
|
||||||
|
import urllib.request
|
||||||
|
import io
|
||||||
|
import math
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import pathlib
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import uuid
|
||||||
|
import glob
|
||||||
|
|
||||||
|
from stitching import tiles_to_equirectangular_blender, multistitch
|
||||||
|
|
||||||
|
def youtube_get_video(url):
|
||||||
|
vid = uuid.uuid4().hex
|
||||||
|
process = subprocess.Popen(
|
||||||
|
['youtube-dl',
|
||||||
|
'--user-agent', '""',
|
||||||
|
'-o', vid,
|
||||||
|
url,
|
||||||
|
], cwd="/tmp/panosteal/youtube/"
|
||||||
|
)
|
||||||
|
|
||||||
|
process.wait()
|
||||||
|
|
||||||
|
data = open('/tmp/panosteal/youtube/%s.mkv' % vid, 'rb').read()
|
||||||
|
|
||||||
|
for i in glob.glob("/tmp/panosteal/youtube/%s*" % vid):
|
||||||
|
os.remove(i)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def youtube_get_file(yurl):
|
||||||
|
urllib.request.urlopen(yurl)
|
||||||
|
|
||||||
|
def youtube_to_equirectangular(url, rotation=[0,0,0], resolution=[3840,1920]):
|
||||||
|
'''
|
||||||
|
Takes the URL of any YouTube video, downloads it and returns the video file.
|
||||||
|
|
||||||
|
:param url: YouTube URL
|
||||||
|
:return: File object containing the video
|
||||||
|
'''
|
||||||
|
|
||||||
|
rx, ry, rz = rotation
|
||||||
|
width, height = resolution
|
||||||
|
|
||||||
|
return youtube_get_video(url)
|
||||||
|
|
||||||
|
process_url = youtube_to_equirectangular
|
Loading…
Reference in a new issue