Compare commits
6 Commits
3946bc53c8
...
0b47bda1aa
Author | SHA1 | Date |
---|---|---|
southerntofu | 0b47bda1aa | |
southerntofu | ee6b30b0c0 | |
southerntofu | 8e473b7948 | |
southerntofu | d51a5089f5 | |
southerntofu | 03ddeb2608 | |
southerntofu | ba55cfd472 |
|
@ -1 +1,2 @@
|
|||
__pycache__/
|
||||
.*.sw*
|
||||
|
|
28
README.md
28
README.md
|
@ -1,4 +1,4 @@
|
|||
### SimpleerTube
|
||||
# SimpleerTube
|
||||
|
||||
Active Known Instances:
|
||||
- https://simpleertube.metalune.xyz
|
||||
|
@ -11,3 +11,29 @@ If you want to visit any page from your PeerTube instance of choice in SimpleerT
|
|||
So, `https://videos.lukesmith.xyz/accounts/luke` becomes `https://simpleertube.metalune.xyz/videos.lukesmith.xyz/accounts/luke`.
|
||||
|
||||
If you visit the main page, you can search globally (it uses [Sepia Search](https://sepiasearch.org) in the backend).
|
||||
|
||||
## Setup
|
||||
|
||||
You need to setup a few dependencies first, usually using pip (`sudo apt install python3-pip` on Debian):
|
||||
|
||||
```
|
||||
$ sudo pip3 install quart bs4 html2text lxml
|
||||
```
|
||||
|
||||
**Note:** If there are other dependencies that are not packaged with your system, please report them to us so they can be added to this README.
|
||||
|
||||
Now you can run a development environment like so:
|
||||
|
||||
```
|
||||
$ python3 main.py # Starts on localhost:5000
|
||||
$ python3 main.py 192.168.42.2 # Starts on 192.168.42.2:5000
|
||||
$ python3 main.py 7171 # Starts on localhost:7171
|
||||
$ python3 main.py 192.168.42.2 7171 # Starts on 192.168.42.2:7171
|
||||
$ python3 main.py ::1 7171 # Also works with IPv6 addresses
|
||||
```
|
||||
|
||||
It is strongly disrecommended to run the production using this command. Instead, please refer to the [Quart deployment docs](https://pgjones.gitlab.io/quart/tutorials/deployment.html).
|
||||
|
||||
## License
|
||||
|
||||
This software is distributed under the AGPLv3 license. You can find a copy in the [LICENSE](LICENSE) file.
|
||||
|
|
40
main.py
40
main.py
|
@ -3,6 +3,7 @@ from datetime import datetime
|
|||
from math import ceil
|
||||
import peertube
|
||||
import html2text
|
||||
import sys
|
||||
|
||||
h2t = html2text.HTML2Text()
|
||||
h2t.ignore_links = True
|
||||
|
@ -11,6 +12,7 @@ h2t.ignore_links = True
|
|||
class VideoWrapper:
|
||||
def __init__(self, a, quality):
|
||||
self.name = a["name"]
|
||||
self.uuid = a["uuid"]
|
||||
self.channel = a["channel"]
|
||||
self.description = a["description"]
|
||||
self.thumbnailPath = a["thumbnailPath"]
|
||||
|
@ -18,6 +20,7 @@ class VideoWrapper:
|
|||
self.category = a["category"]
|
||||
self.licence = a["licence"]
|
||||
self.language = a["language"]
|
||||
self.captions = a["captions"]
|
||||
self.privacy = a["privacy"]
|
||||
self.tags = a["tags"]
|
||||
|
||||
|
@ -128,6 +131,11 @@ async def simpleer_search_redirect():
|
|||
query = (await request.form)["query"]
|
||||
return redirect("/search/" + query)
|
||||
|
||||
@app.route("/search", methods = ["GET"])
|
||||
async def simpleer_search_get_redirect():
|
||||
query = request.args.get("query")
|
||||
return redirect("/search/" + query)
|
||||
|
||||
@app.route("/search/<string:query>", defaults = {"page": 1})
|
||||
@app.route("/search/<string:query>/<int:page>")
|
||||
async def simpleer_search(query, page):
|
||||
|
@ -148,6 +156,13 @@ async def simpleer_search(query, page):
|
|||
|
||||
@app.route("/<string:domain>/")
|
||||
async def instance(domain):
|
||||
# favicon.ico is not a domain name
|
||||
if domain == "favicon.ico":
|
||||
return await render_template(
|
||||
"error.html",
|
||||
error_number = "404",
|
||||
error_reason = "We don't have a favicon yet. If you would like to contribute one, please send it to ~metalune/public-inbox@lists.sr.ht"
|
||||
), 404
|
||||
return redirect("/" + domain + "/videos/trending")
|
||||
|
||||
@app.route("/<string:domain>/videos/local", defaults = {"page": 1})
|
||||
|
@ -250,6 +265,7 @@ async def search(domain, term, page):
|
|||
@app.route("/<string:domain>/videos/watch/<string:id>/")
|
||||
async def video(domain, id):
|
||||
data = peertube.video(domain, id)
|
||||
data["captions"] = peertube.video_captions(domain, id)
|
||||
quality = request.args.get("quality")
|
||||
embed = request.args.get("embed")
|
||||
vid = VideoWrapper(data, quality)
|
||||
|
@ -281,7 +297,6 @@ async def video(domain, id):
|
|||
embed=embed,
|
||||
)
|
||||
|
||||
|
||||
def build_channel_or_account_name(domain, name):
|
||||
if '@' in name:
|
||||
return name
|
||||
|
@ -396,5 +411,26 @@ async def video_channels__about(domain, name):
|
|||
about = peertube.video_channel(domain, name)
|
||||
)
|
||||
|
||||
# --- Subtitles/captions proxying ---
|
||||
@app.route("/<string:domain>/videos/watch/<string:id>/<string:lang>.vtt")
|
||||
async def subtitles(domain, id, lang):
|
||||
try:
|
||||
return peertube.video_captions_download(domain, id, lang)
|
||||
except Exception as e:
|
||||
return await render_template(
|
||||
"error.html",
|
||||
error_number = "500",
|
||||
error_reason = e
|
||||
), 500
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
if len(sys.argv) == 3:
|
||||
interface = sys.argv[1]
|
||||
port = sys.argv[2]
|
||||
elif len(sys.argv) == 2:
|
||||
interface = "127.0.0.1"
|
||||
port = sys.argv[1]
|
||||
else:
|
||||
interface = "127.0.0.1"
|
||||
port = "5000"
|
||||
app.run(host=interface, port=port)
|
||||
|
|
10
peertube.py
10
peertube.py
|
@ -22,6 +22,16 @@ def video(domain, id):
|
|||
url = "https://" + domain + "/api/v1/videos/" + id
|
||||
return json.loads(requests.get(url).text)
|
||||
|
||||
def video_captions(domain, id):
|
||||
url = "https://" + domain + "/api/v1/videos/" + id + "/captions"
|
||||
return json.loads(requests.get(url).text)
|
||||
|
||||
def video_captions_download(domain, id, lang):
|
||||
# URL is hardcoded to prevent further proxying. URL may change with updates, see captions API
|
||||
# eg. https://kolektiva.media/api/v1/videos/9c9de5e8-0a1e-484a-b099-e80766180a6d/captions
|
||||
url = "https://" + domain + "/lazy-static/video-captions/" + id + '-' + lang + ".vtt"
|
||||
return requests.get(url).text
|
||||
|
||||
def search(domain, term, start=0, count=10):
|
||||
url = "https://" + domain + "/api/v1/search/videos?start=" + str(start) + "&count=" + str(count) + "&search=" + term + "&sort=-match&searchTarget=local"
|
||||
return json.loads(requests.get(url).text)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}ERROR: {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Error {{ error_number }}</h1>
|
||||
<p>{{ error_reason }}</p>
|
||||
{% endblock %}
|
|
@ -22,7 +22,8 @@ By:
|
|||
<b>Resolutions:</b>
|
||||
{% else %}
|
||||
<video height="300" style="max-width: 100%" controls>
|
||||
<source src="{{ video.video }}">
|
||||
<source src="{{ video.video }}">{% for track in video.captions.data %}
|
||||
<track kind="subtitles" srclang="{{ track.language.id }}" label="{{ track.language.label }}" src="/{{ domain }}/videos/watch/{{ video.uuid }}/{{ track.language.id }}.vtt">{% endfor %}
|
||||
</video>
|
||||
<br>
|
||||
<b>Resolutions:</b>
|
||||
|
|
Loading…
Reference in New Issue