Play button + autoplay on press

This commit is contained in:
2025-12-13 08:35:54 +01:00
parent 14e417d72a
commit 9ce74da4ec
3 changed files with 467 additions and 336 deletions

View File

@@ -12,7 +12,7 @@ serving
this scraper will download the gif and mp3 from a ytmnd and write a file embedding these things in addition to zoom text (if any). this scraper will download the gif and mp3 from a ytmnd and write a file embedding these things in addition to zoom text (if any).
The downloaded files cannot be loaded from a `file://` url. In order to view these files, put them online or run a local server. For example, `python -m http.server` from the directory and got to [http://localhost:8000/](http://localhost:8000/). If you host them somewhere, remember to include `ytmnd.js` in the same directory. The downloaded files cannot be loaded from a `file://` url. In order to view these files, put them online or run a local server. For example, `python -m http.server` from the directory and got to [http://localhost:8000/](http://localhost:8000/).
options options
------- -------

View File

@@ -1,43 +0,0 @@
(function () {
var audio = new Audio(url);
audio.loop = true;
audio.muted = true;
audio
.play()
.then(function () {
console.log("Audio started (muted). Click/tap to unmute!");
var unmuteMsg = document.createElement("div");
unmuteMsg.textContent = "Click to unmute";
unmuteMsg.style.cssText =
"position:fixed;top:10px;right:10px;background:rgba(0,0,0,0.8);color:#fff;padding:10px 20px;border-radius:5px;font-family:sans-serif;z-index:9999;cursor:pointer;";
document.body.appendChild(unmuteMsg);
function unmute() {
audio.muted = false;
unmuteMsg.remove();
console.log("Audio unmuted!");
}
document.addEventListener("click", unmute, { once: true });
document.addEventListener("keydown", unmute, { once: true });
document.addEventListener("touchstart", unmute, { once: true });
unmuteMsg.addEventListener("click", unmute, { once: true });
})
.catch(function (error) {
console.error("Autoplay failed even when muted:", error);
function playOnInteraction() {
audio.muted = false;
audio
.play()
.then(function () {
console.log("Audio started after user interaction");
})
.catch(function (err) {
console.error("Still couldn't play:", err);
});
}
document.addEventListener("click", playOnInteraction, { once: true });
document.addEventListener("keydown", playOnInteraction, { once: true });
document.addEventListener("touchstart", playOnInteraction, {
once: true,
});
});
})();

758
ytmndd.py
View File

@@ -1,311 +1,485 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import sys import json
import os import os
import os.path import os.path
import re import re
import time
import json
import subprocess import subprocess
import sys
import time
from optparse import OptionParser from optparse import OptionParser
import requests import requests
from requests.exceptions import RequestException from requests.exceptions import RequestException
class YTMND: class YTMND:
def __init__(self):
self.user_mode = False
self.media_only = False
self.html_only = False
self.json_only = False
self.no_web_audio = False
self.print_json = False
self.sleep = 5
def __init__(self): def fetch_user(self, user):
self.user_mode = False if user == "":
self.media_only = False print("expecting one ytmnd name, got " + str(sys.argv))
self.html_only = False return
self.json_only = False
self.no_web_audio = False
self.print_json = False
self.sleep = 5
def fetch_user(self, user): ytmnd_name = user
if user == "": try:
print("expecting one ytmnd name, got " + str(sys.argv)) response = requests.get(
return "http://ytmnd.com/users/" + ytmnd_name + "/sites",
headers={"User-Agent": "Mozilla/5.0"},
)
response.raise_for_status()
ytmnd_html = response.text.splitlines()
except RequestException as e:
print(f"Error fetching user page: {e}")
return
ytmnd_name = user domains = []
try:
response = requests.get("http://ytmnd.com/users/" + ytmnd_name + "/sites",
headers={'User-Agent': 'Mozilla/5.0'})
response.raise_for_status()
ytmnd_html = response.text.splitlines()
except RequestException as e:
print(f"Error fetching user page: {e}")
return
domains = [] for line in ytmnd_html:
if "profile_link" in line:
expr = r"site_link\" href=\"http://(\S+).ytmn(d|sfw)?.com\""
match = re.search(expr, line)
if match:
domain = match.group(1)
domains.append(domain)
for line in ytmnd_html: if self.json_only:
if 'profile_link' in line: if self.media_only:
expr = r"site_link\" href=\"http://(\S+).ytmn(d|sfw)?.com\"" os.makedirs(user, exist_ok=True)
match = re.search(expr, line) os.chdir(user)
if match: parsed = []
domain = match.group(1) for domain in domains:
domains.append(domain) result = self.fetch_ytmnd(domain)
if result:
parsed.append(result)
if self.media_only:
os.chdir("..")
self.write_json(ytmnd_name, parsed)
if self.json_only: else:
if self.media_only: print(">> found %d domains" % len(domains))
os.makedirs(user, exist_ok=True) os.makedirs(user, exist_ok=True)
os.chdir(user) os.chdir(user)
parsed = [] for domain in domains:
for domain in domains: self.fetch_ytmnd(domain)
result = self.fetch_ytmnd(domain) os.chdir("..")
if result:
parsed.append(result) def fetch_ytmnd(self, domain):
if self.media_only: if domain == "":
os.chdir("..") print("expecting one ytmnd name, got " + str(sys.argv))
self.write_json(ytmnd_name, parsed) return None
if not self.print_json:
print("fetching %s" % domain)
if self.sleep:
time.sleep(self.sleep)
ytmnd_name = domain
try:
response = requests.get(
"http://" + domain + ".ytmnd.com", headers={"User-Agent": "Mozilla/5.0"}
)
response.raise_for_status()
ytmnd_html = response.text
expr = r"ytmnd.site_id = (\d+);"
match = re.search(expr, ytmnd_html)
if not match:
print(f"Could not find site_id for {domain}")
return None
ytmnd_id = match.group(1)
response = requests.get(
"http://" + domain + ".ytmnd.com/info/" + ytmnd_id + "/json",
headers={"User-Agent": "Mozilla/5.0"},
)
response.raise_for_status()
ytmnd_info = response.json()
except RequestException as e:
print(f"Error fetching {domain}: {e}")
return None
if self.print_json:
print(json.dumps(ytmnd_info, sort_keys=True, indent=4))
elif self.json_only:
if self.media_only:
self.fetch_media(ytmnd_info)
return self.parse_json(ytmnd_info)
elif self.media_only:
self.fetch_media(ytmnd_info)
elif self.html_only:
self.write_index(ytmnd_info)
else:
self.fetch_media(ytmnd_info)
self.write_index(ytmnd_info)
return ytmnd_info
def fetch_media(self, ytmnd_info):
domain = ytmnd_info["site"]["domain"]
original_gif = ytmnd_info["site"]["foreground"]["url"]
gif_type = original_gif.split(".")[-1]
original_wav = ytmnd_info["site"]["sound"]["url"]
wav_type = ytmnd_info["site"]["sound"]["type"]
if "alternates" in ytmnd_info["site"]["sound"]:
key = list(ytmnd_info["site"]["sound"]["alternates"].keys())[0]
value = ytmnd_info["site"]["sound"]["alternates"][key]
if value["file_type"] != "swf":
original_wav = value["file_url"]
wav_type = ytmnd_info["site"]["sound"]["file_type"]
subprocess.run(["wget", "--quiet", "-O", f"{domain}.{gif_type}", original_gif])
subprocess.run(["wget", "--quiet", "-O", f"{domain}.{wav_type}", original_wav])
def write_index(self, ytmnd_info):
domain = ytmnd_info["site"]["domain"]
bgcolor = ytmnd_info["site"]["background"]["color"]
title = ytmnd_info["site"]["description"]
placement = ytmnd_info["site"]["foreground"]["placement"]
original_gif = ytmnd_info["site"]["foreground"]["url"]
gif_type = original_gif.split(".")[-1]
wav_type = ytmnd_info["site"]["sound"]["type"]
if "alternates" in ytmnd_info["site"]["sound"]:
key = list(ytmnd_info["site"]["sound"]["alternates"].keys())[0]
value = ytmnd_info["site"]["sound"]["alternates"][key]
if value["file_type"] != "swf":
original_wav = value["file_url"]
wav_type = ytmnd_info["site"]["sound"]["file_type"]
with open(domain + ".html", "w", encoding="utf-8") as fn:
fn.write("<!DOCTYPE html>\n")
fn.write("<html>\n")
fn.write("<head>\n")
fn.write("<meta charset='utf-8'>\n")
fn.write(
"<meta name='viewport' content='width=device-width, initial-scale=1.0'>\n"
)
fn.write("<title>%s</title>\n" % title)
fn.write("<style>\n")
fn.write("*{margin:0;padding:0;width:100%;height:100%;}\n")
fn.write(
"body{font-size:12px;font-weight:normal;font-style:normal;overflow:hidden;"
)
fn.write("background-color:%s;" % bgcolor)
fn.write("background-image:url(%s.%s);" % (domain, gif_type))
if placement == "mc":
fn.write(
"background-position: center center; background-repeat: no-repeat;}"
)
elif placement == "tile":
fn.write("background-position: top left; background-repeat: repeat;}")
fn.write("\n")
fn.write(
"#zoom_text{position:absolute;left:0;top:0;width:1000px;z-index:10;text-align:center;font-family:Tahoma, sans-serif}\n"
)
fn.write("#zoom_text div{position:absolute;width:1000px}\n")
fn.write(
"#unmute-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;z-index:99999;cursor:pointer;}\n"
)
fn.write(
"#unmute-btn{width:80px;height:80px;background:rgba(255,255,255,0.1);border:2px solid rgba(255,255,255,0.3);border-radius:50%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;font-size:32px;color:rgba(255,255,255,0.9);-webkit-transition:all 0.3s ease;transition:all 0.3s ease;}\n"
)
fn.write(
"#unmute-btn:hover{background:rgba(255,255,255,0.2);border-color:rgba(255,255,255,0.5);-webkit-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1);}\n"
)
fn.write("</style>\n")
fn.write("</head>\n")
fn.write("<body>\n")
fn.write('<div id="unmute-overlay">\n')
fn.write(' <div id="unmute-btn">▶</div>\n')
fn.write("</div>\n")
self.write_zoom_text(fn, ytmnd_info)
if self.no_web_audio:
fn.write("<audio src='%s.%s' loop autoplay>\n" % (domain, wav_type))
fn.write("</body>\n")
fn.write("<script>\n")
fn.write("(function() {\n")
fn.write(" var audioUrl = '%s.%s';\n" % (domain, wav_type))
fn.write(" var context = null;\n")
fn.write(" var source = null;\n")
fn.write(" var audioBuffer = null;\n")
fn.write(" var isPlaying = false;\n")
fn.write(" var fallbackAudio = null;\n")
fn.write(" \n")
fn.write(" function hasWebAudio() {\n")
fn.write(
" return ('AudioContext' in window) || ('webkitAudioContext' in window);\n"
)
fn.write(" }\n")
fn.write(" \n")
fn.write(" function createContext() {\n")
fn.write(" if ('AudioContext' in window) {\n")
fn.write(" return new AudioContext();\n")
fn.write(" } else if ('webkitAudioContext' in window) {\n")
fn.write(" return new webkitAudioContext();\n")
fn.write(" }\n")
fn.write(" return null;\n")
fn.write(" }\n")
fn.write(" \n")
fn.write(" function loadAudioWithXHR(callback, errorCallback) {\n")
fn.write(" var request = new XMLHttpRequest();\n")
fn.write(" request.open('GET', audioUrl, true);\n")
fn.write(" request.responseType = 'arraybuffer';\n")
fn.write(" request.onload = function() {\n")
fn.write(" if (request.status === 200) {\n")
fn.write(" callback(request.response);\n")
fn.write(" } else {\n")
fn.write(
" errorCallback('Request failed with status: ' + request.status);\n"
)
fn.write(" }\n")
fn.write(" };\n")
fn.write(" request.onerror = function() {\n")
fn.write(" errorCallback('Network error');\n")
fn.write(" };\n")
fn.write(" request.send();\n")
fn.write(" }\n")
fn.write(" \n")
fn.write(" function loopAudio() {\n")
fn.write(" if (!isPlaying || !audioBuffer) return;\n")
fn.write(" \n")
fn.write(" source = context.createBufferSource();\n")
fn.write(" source.connect(context.destination);\n")
fn.write(" source.buffer = audioBuffer;\n")
fn.write(" \n")
fn.write(" try {\n")
fn.write(" if (source.start) {\n")
fn.write(" source.start(0);\n")
fn.write(" } else if (source.noteOn) {\n")
fn.write(" source.noteOn(0);\n")
fn.write(" }\n")
fn.write(" } catch(e) {\n")
fn.write(" console.error('Start error:', e);\n")
fn.write(" }\n")
fn.write(" \n")
fn.write(" var duration = audioBuffer.duration * 1000;\n")
fn.write(" var offset = audioBuffer.duration < 2 ? 0 : 60;\n")
fn.write(" setTimeout(loopAudio, duration - offset);\n")
fn.write(" }\n")
fn.write(" \n")
fn.write(" function playWebAudio() {\n")
fn.write(" context = createContext();\n")
fn.write(" if (!context) {\n")
fn.write(" fallbackToHTMLAudio();\n")
fn.write(" return;\n")
fn.write(" }\n")
fn.write(" \n")
fn.write(" if (context.state === 'suspended') {\n")
fn.write(" context.resume();\n")
fn.write(" }\n")
fn.write(" \n")
fn.write(" loadAudioWithXHR(\n")
fn.write(" function(arrayBuffer) {\n")
fn.write(" context.decodeAudioData(\n")
fn.write(" arrayBuffer,\n")
fn.write(" function(buffer) {\n")
fn.write(" audioBuffer = buffer;\n")
fn.write(" isPlaying = true;\n")
fn.write(" loopAudio();\n")
fn.write(" },\n")
fn.write(" function(error) {\n")
fn.write(" console.error('Decode error:', error);\n")
fn.write(" fallbackToHTMLAudio();\n")
fn.write(" }\n")
fn.write(" );\n")
fn.write(" },\n")
fn.write(" function(error) {\n")
fn.write(" console.error('Load error:', error);\n")
fn.write(" fallbackToHTMLAudio();\n")
fn.write(" }\n")
fn.write(" );\n")
fn.write(" }\n")
fn.write(" \n")
fn.write(" function fallbackToHTMLAudio() {\n")
fn.write(" try {\n")
fn.write(" fallbackAudio = new Audio(audioUrl);\n")
fn.write(" fallbackAudio.loop = true;\n")
fn.write(" var playPromise = fallbackAudio.play();\n")
fn.write(" if (playPromise && playPromise.catch) {\n")
fn.write(" playPromise.catch(function(error) {\n")
fn.write(" console.error('HTML5 audio play failed:', error);\n")
fn.write(" });\n")
fn.write(" }\n")
fn.write(" isPlaying = true;\n")
fn.write(" } catch(e) {\n")
fn.write(" console.error('Fallback audio failed:', e);\n")
fn.write(" }\n")
fn.write(" }\n")
fn.write(" \n")
fn.write(" function startAudio() {\n")
fn.write(" var overlay = document.getElementById('unmute-overlay');\n")
fn.write(" if (overlay) {\n")
fn.write(" overlay.style.display = 'none';\n")
fn.write(" }\n")
fn.write(" \n")
fn.write(" if (hasWebAudio()) {\n")
fn.write(" playWebAudio();\n")
fn.write(" } else {\n")
fn.write(" fallbackToHTMLAudio();\n")
fn.write(" }\n")
fn.write(" }\n")
fn.write(" \n")
fn.write(" var overlay = document.getElementById('unmute-overlay');\n")
fn.write(" if (overlay) {\n")
fn.write(" overlay.addEventListener('click', startAudio);\n")
fn.write(" overlay.addEventListener('touchstart', function(e) {\n")
fn.write(" e.preventDefault();\n")
fn.write(" startAudio();\n")
fn.write(" });\n")
fn.write(" }\n")
fn.write(" \n")
fn.write(" document.addEventListener('keydown', function(e) {\n")
fn.write(" if (overlay && overlay.style.display !== 'none') {\n")
fn.write(" startAudio();\n")
fn.write(" }\n")
fn.write(" });\n")
fn.write("})();\n")
fn.write("</script>\n")
fn.write("<script type='application/json' id='ytmnd-data'>\n")
fn.write(json.dumps(ytmnd_info, sort_keys=True, indent=2) + "\n")
fn.write("</script>\n")
fn.write("</html>")
def write_zoom_text(self, fn, ytmnd_info):
if "zoom_text" not in ytmnd_info["site"]:
return
zoom_text = ytmnd_info["site"]["zoom_text"]
fn.write('<div id="zoom_text">')
offset = 100
if "line_3" in zoom_text and len(zoom_text["line_3"]) > 0:
self.write_zoom_layers(fn, zoom_text["line_3"], offset, 269)
offset += 21
if "line_2" in zoom_text and len(zoom_text["line_2"]) > 0:
self.write_zoom_layers(fn, zoom_text["line_2"], offset, 135)
offset += 21
if "line_1" in zoom_text and len(zoom_text["line_1"]) > 0:
self.write_zoom_layers(fn, zoom_text["line_1"], offset, 1)
fn.write("</div>")
def write_zoom_layers(self, fn, text, offset, top):
for i in range(1, 22):
z_index = offset + i
row_left = i * 2
row_top = top + i
font_size = i * 2
if i == 21:
color = 0
else:
color = i * 4
fn.write(
"<div style='z-index: %d; left: %dpx; top: %dpx; color: rgb(%d, %d, %d); font-size: %dpt;'>%s</div>"
% (z_index, row_left, row_top, color, color, color, font_size, text)
)
def parse_json(self, ytmnd_info):
domain = ytmnd_info["site"]["domain"]
bgcolor = ytmnd_info["site"]["background"]["color"]
title = ytmnd_info["site"]["description"]
placement = ytmnd_info["site"]["foreground"]["placement"]
gif_type = ytmnd_info["site"]["foreground"]["url"].split(".")[-1]
wav_type = ytmnd_info["site"]["sound"]["type"]
zoom_text = ytmnd_info["site"]["zoom_text"]
keywords = ytmnd_info["site"]["keywords"]
username = ytmnd_info["site"]["user"]["user_name"]
sound_origin = ytmnd_info["site"]["sound_origin"]
image_origin = ytmnd_info["site"]["fg_image_origin"]
work_safe = ytmnd_info["site"]["work_safe"]
if len(zoom_text["line_1"]) == 0:
zoom_text = ""
if "alternates" in ytmnd_info["site"]["sound"]:
key = list(ytmnd_info["site"]["sound"]["alternates"].keys())[0]
value = ytmnd_info["site"]["sound"]["alternates"][key]
if value["file_type"] != "swf":
wav_type = ytmnd_info["site"]["sound"]["file_type"]
simplified_info = {
"domain": domain,
"title": title,
"username": username,
"work_safe": work_safe,
"bgcolor": bgcolor,
"placement": placement,
"zoom_text": zoom_text,
"image": domain + "." + gif_type,
"sound": domain + "." + wav_type,
"image_type": gif_type,
"sound_type": wav_type,
"image_origin": image_origin,
"sound_origin": sound_origin,
}
return simplified_info
def write_json(self, domain, data):
with open(domain + ".json", "w", encoding="utf-8") as fn:
fn.write(json.dumps(data))
if __name__ == "__main__":
parser = OptionParser()
parser.add_option("-u", "--user", action="store_true")
parser.add_option("-m", "--media-only", action="store_true")
parser.add_option("-f", "--html-only", action="store_true")
parser.add_option("-j", "--json-only", action="store_true")
parser.add_option("-w", "--no-web-audio", action="store_true")
parser.add_option("-p", "--print-json", action="store_true")
parser.add_option(
"-s", "--sleep", action="store", type="int", dest="sleep", default=5
)
(options, args) = parser.parse_args()
if len(args) == 0:
parser.error("incorrect number of arguments")
sys.exit(1)
ytmnd = YTMND()
ytmnd.user_mode = options.user
ytmnd.media_only = options.media_only
ytmnd.html_only = options.html_only
ytmnd.json_only = options.json_only
ytmnd.no_web_audio = options.no_web_audio
ytmnd.print_json = options.print_json
ytmnd.sleep = options.sleep
if options.user:
user = args[0]
ytmnd.fetch_user(user)
else: else:
print(">> found %d domains" % len(domains)) name = (
os.makedirs(user, exist_ok=True) args[0]
os.chdir(user) .replace("http://", "")
if not self.no_web_audio: .replace(".ytmnsfw.com", "")
self.copy_ytmnd_js() .replace(".ytmnd.com", "")
for domain in domains: .replace("/", "")
self.fetch_ytmnd(domain) )
os.chdir("..") ytmnd.fetch_ytmnd(name)
def fetch_ytmnd(self, domain):
if domain == "":
print("expecting one ytmnd name, got " + str(sys.argv))
return None
if not self.print_json:
print("fetching %s" % domain)
if self.sleep:
time.sleep(self.sleep)
ytmnd_name = domain
try:
response = requests.get("http://" + domain + ".ytmnd.com",
headers={'User-Agent': 'Mozilla/5.0'})
response.raise_for_status()
ytmnd_html = response.text
expr = r"ytmnd.site_id = (\d+);"
match = re.search(expr, ytmnd_html)
if not match:
print(f"Could not find site_id for {domain}")
return None
ytmnd_id = match.group(1)
response = requests.get("http://" + domain + ".ytmnd.com/info/" + ytmnd_id + "/json",
headers={'User-Agent': 'Mozilla/5.0'})
response.raise_for_status()
ytmnd_info = response.json()
except RequestException as e:
print(f"Error fetching {domain}: {e}")
return None
if self.print_json:
print(json.dumps(ytmnd_info, sort_keys=True, indent=4))
elif self.json_only:
if self.media_only:
self.fetch_media(ytmnd_info)
return self.parse_json(ytmnd_info)
elif self.media_only:
self.fetch_media(ytmnd_info)
elif self.html_only:
self.write_index(ytmnd_info)
else:
self.fetch_media(ytmnd_info)
self.write_index(ytmnd_info)
return ytmnd_info
def fetch_media(self, ytmnd_info):
domain = ytmnd_info['site']['domain']
original_gif = ytmnd_info['site']['foreground']['url']
gif_type = original_gif.split(".")[-1]
original_wav = ytmnd_info['site']['sound']['url']
wav_type = ytmnd_info['site']['sound']['type']
if 'alternates' in ytmnd_info['site']['sound']:
key = list(ytmnd_info['site']['sound']['alternates'].keys())[0]
value = ytmnd_info['site']['sound']['alternates'][key]
if value['file_type'] != 'swf':
original_wav = value['file_url']
wav_type = ytmnd_info['site']['sound']['file_type']
subprocess.run(["wget", "--quiet", "-O", f"{domain}.{gif_type}", original_gif])
subprocess.run(["wget", "--quiet", "-O", f"{domain}.{wav_type}", original_wav])
def write_index(self, ytmnd_info):
domain = ytmnd_info['site']['domain']
bgcolor = ytmnd_info['site']['background']['color']
title = ytmnd_info['site']['description']
placement = ytmnd_info['site']['foreground']['placement']
original_gif = ytmnd_info['site']['foreground']['url']
gif_type = original_gif.split(".")[-1]
wav_type = ytmnd_info['site']['sound']['type']
if 'alternates' in ytmnd_info['site']['sound']:
key = list(ytmnd_info['site']['sound']['alternates'].keys())[0]
value = ytmnd_info['site']['sound']['alternates'][key]
if value['file_type'] != 'swf':
original_wav = value['file_url']
wav_type = ytmnd_info['site']['sound']['file_type']
with open(domain + ".html", 'w', encoding='utf-8') as fn:
fn.write("<html>\n")
fn.write("<head>\n")
fn.write("<title>%s</title>\n" % title)
fn.write("<style>\n")
fn.write("*{margin:0;padding:0;width:100%;height:100%;}\n")
fn.write("body{font-size:12px;font-weight:normal;font-style:normal;overflow:hidden;")
fn.write("background-color:%s;" % bgcolor)
fn.write("background-image:url(%s.%s);" % (domain, gif_type))
if placement == "mc":
fn.write("background-position: center center; background-repeat: no-repeat;}")
elif placement == "tile":
fn.write("background-position: top left; background-repeat: repeat;}")
fn.write("\n")
fn.write("#zoom_text{position:absolute;left:0;top:0;width:1000px;z-index:10;text-align:center;font-family:Tahoma, sans-serif}")
fn.write("#zoom_text div{position:absolute;width:1000px}")
fn.write("</style>\n")
fn.write("</head>\n")
fn.write("<body>\n")
self.write_zoom_text(fn, ytmnd_info)
if self.no_web_audio:
fn.write("<audio src='%s.%s' loop autoplay>\n" % (domain, wav_type))
fn.write("</body>\n")
else:
fn.write("</body>\n")
fn.write("<script>var url = '%s.%s'</script>\n" % (domain, wav_type))
fn.write("<script src='ytmnd.js'></script>\n")
fn.write("<script type='application/json'>\n")
fn.write(json.dumps(ytmnd_info, sort_keys=True, indent=4) + "\n")
fn.write("</script>\n")
fn.write("</html>")
def write_zoom_text(self, fn, ytmnd_info):
if 'zoom_text' not in ytmnd_info['site']:
return
zoom_text = ytmnd_info['site']['zoom_text']
fn.write('<div id="zoom_text">')
offset = 100
if "line_3" in zoom_text and len(zoom_text["line_3"]) > 0:
self.write_zoom_layers(fn, zoom_text['line_3'], offset, 269)
offset += 21
if "line_2" in zoom_text and len(zoom_text["line_2"]) > 0:
self.write_zoom_layers(fn, zoom_text['line_2'], offset, 135)
offset += 21
if "line_1" in zoom_text and len(zoom_text["line_1"]) > 0:
self.write_zoom_layers(fn, zoom_text['line_1'], offset, 1)
fn.write('</div>')
def write_zoom_layers(self, fn, text, offset, top):
for i in range(1, 22):
z_index = offset + i
row_left = i * 2
row_top = top + i
font_size = i * 2
if i == 21:
color = 0
else:
color = i * 4
fn.write("<div style='z-index: %d; left: %dpx; top: %dpx; color: rgb(%d, %d, %d); font-size: %dpt;'>%s</div>"
% (z_index, row_left, row_top, color, color, color, font_size, text))
def copy_ytmnd_js(self):
if not os.path.isfile("ytmnd.js"):
parent_js = os.path.join("..", "ytmnd.js")
if os.path.isfile(parent_js):
subprocess.run(["cp", parent_js, "."])
def parse_json(self, ytmnd_info):
domain = ytmnd_info['site']['domain']
bgcolor = ytmnd_info['site']['background']['color']
title = ytmnd_info['site']['description']
placement = ytmnd_info['site']['foreground']['placement']
gif_type = ytmnd_info['site']['foreground']['url'].split(".")[-1]
wav_type = ytmnd_info['site']['sound']['type']
zoom_text = ytmnd_info['site']['zoom_text']
keywords = ytmnd_info['site']['keywords']
username = ytmnd_info['site']['user']['user_name']
sound_origin = ytmnd_info['site']['sound_origin']
image_origin = ytmnd_info['site']['fg_image_origin']
work_safe = ytmnd_info['site']['work_safe']
if len(zoom_text['line_1']) == 0:
zoom_text = ""
if 'alternates' in ytmnd_info['site']['sound']:
key = list(ytmnd_info['site']['sound']['alternates'].keys())[0]
value = ytmnd_info['site']['sound']['alternates'][key]
if value['file_type'] != 'swf':
wav_type = ytmnd_info['site']['sound']['file_type']
simplified_info = {
'domain': domain,
'title': title,
'username': username,
'work_safe': work_safe,
'bgcolor': bgcolor,
'placement': placement,
'zoom_text': zoom_text,
'image': domain + "." + gif_type,
'sound': domain + "." + wav_type,
'image_type': gif_type,
'sound_type': wav_type,
'image_origin': image_origin,
'sound_origin': sound_origin,
}
return simplified_info
def write_json(self, domain, data):
with open(domain + '.json', 'w', encoding='utf-8') as fn:
fn.write(json.dumps(data))
if __name__ == '__main__':
parser = OptionParser()
parser.add_option("-u", "--user", action="store_true")
parser.add_option("-m", "--media-only", action="store_true")
parser.add_option("-f", "--html-only", action="store_true")
parser.add_option("-j", "--json-only", action="store_true")
parser.add_option("-w", "--no-web-audio", action="store_true")
parser.add_option("-p", "--print-json", action="store_true")
parser.add_option("-s", "--sleep", action="store", type="int", dest="sleep", default=5)
(options, args) = parser.parse_args()
if len(args) == 0:
parser.error("incorrect number of arguments")
sys.exit(1)
ytmnd = YTMND()
ytmnd.user_mode = options.user
ytmnd.media_only = options.media_only
ytmnd.html_only = options.html_only
ytmnd.json_only = options.json_only
ytmnd.no_web_audio = options.no_web_audio
ytmnd.print_json = options.print_json
ytmnd.sleep = options.sleep
if options.user:
user = args[0]
ytmnd.fetch_user(user)
else:
name = args[0].replace("http://","").replace(".ytmnsfw.com","").replace(".ytmnd.com","").replace("/","")
ytmnd.fetch_ytmnd(name)