Compare commits

...

6 Commits

Author SHA1 Message Date
southerntofu 0b47bda1aa /search supports GET forms, as required by OpenSearch 2021-07-30 19:20:27 +02:00
southerntofu ee6b30b0c0 Propose subtitles in the native video player 2021-07-30 19:18:57 +02:00
southerntofu 8e473b7948 Don't commit vim temporary files 2021-07-30 19:16:52 +02:00
southerntofu d51a5089f5 Don't traceback when favicon.ico is requested, display a nice error instead 2021-07-30 19:16:08 +02:00
southerntofu 03ddeb2608 Support custom interface/port in main.py 2021-07-30 19:15:23 +02:00
southerntofu ba55cfd472 Update the README 2021-07-30 19:09:39 +02:00
6 changed files with 86 additions and 4 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
__pycache__/
.*.sw*

View File

@ -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
View File

@ -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)

View File

@ -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)

8
templates/error.html Normal file
View File

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block title %}ERROR: {% endblock %}
{% block content %}
<h1>Error {{ error_number }}</h1>
<p>{{ error_reason }}</p>
{% endblock %}

View File

@ -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>