Added user data and Google OAuth

Added initial implementation of user data for tv shows and movies as well as OAuth for Google sign in.
This commit is contained in:
Matthew Welch 2020-03-19 19:25:44 -07:00
parent f478c29f34
commit 1fb4a869b0
172 changed files with 14892 additions and 27074 deletions

View File

@ -1,9 +1,7 @@
from flask import Blueprint, flash, redirect, url_for, render_template
from flask_login import login_required, current_user
from sqlite_web import sqlite_web
Admin = Blueprint("admin", __name__, template_folder="templates")
login_required(sqlite_web.index)
@Admin.route("/admin")

View File

@ -26,7 +26,6 @@ def index():
return render_template("comics/index.html", title="Comics", publishers=publishers, page=page, max_items=max_items, start=start, end=end, item_count=len(publishers))
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
return str(type(e)) + " " + str(e)
@ -70,7 +69,6 @@ def search():
publisher_end=publisher_end, series_end=series_end, comics_end=comics_end)
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
return str(type(e))+" "+str(e)
@ -102,7 +100,6 @@ def comics_publisher(publisher):
start=start, end=end, page=page, max_items=max_items, item_count=len(publisher_series))
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
return str(type(e)) + " " + str(e)
@ -115,7 +112,7 @@ def comic_viewer(publisher, series, series_year, issue):
series_parsed = parse.quote(series)
page_number = int(request.args.get("pageNumber"))
meta = database.db_get_comic(publisher, series, series_year, issue)
page_count = int(meta["pageCount"])
page_count = int(meta.pageCount)
prev_page = page_number - 1
next_page = page_number + 1
@ -125,11 +122,10 @@ def comic_viewer(publisher, series, series_year, issue):
prev_page = page_count - 1
prev_url = "/comics/{}?series={}&seriesYear={}&issue={}&pageNumber={}".format(publisher_parsed, series_parsed, series_year, issue, prev_page)
next_url = "/comics/{}?series={}&seriesYear={}&issue={}&pageNumber={}".format(publisher_parsed, series_parsed, series_year, issue, next_page)
title = "Comics: "+meta["series"]+": #"+meta["issueText"]+" "+(meta["title"] or "")
title = "Comics: "+meta.series+": #"+meta.issueText+" "+(meta.title or "")
return render_template("comics/comicView.html", title=title, on_mobile=on_mobile, prev_url=prev_url, next_url=next_url, comic=meta, page_number=page_number)
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
return str(type(e)) + " " + str(e)
@ -139,11 +135,10 @@ def comic_gallery(publisher, series, series_year, issue):
max_items = request.args.get("max_items", 30, type=int)
meta = database.db_get_comic(publisher, series, series_year, issue)
start = (max_items*(page-1))
end = meta["pageCount"] if meta["pageCount"] < max_items*page else max_items*page
return render_template("comics/comicGallery.html", title="Comics", comic=meta, start=start, end=end, page=page, max_items=max_items, item_count=meta["pageCount"])
end = meta.pageCount if meta.pageCount < max_items*page else max_items*page
return render_template("comics/comicGallery.html", title="Comics", comic=meta, start=start, end=end, page=page, max_items=max_items, item_count=meta.pageCount)
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
return str(type(e)) + " " + str(e)
@ -151,12 +146,12 @@ def comic_gallery(publisher, series, series_year, issue):
@login_required
def get_comic_page(comic_id, page_number):
meta = database.db_get_comic_by_id(comic_id)
comic = func.open_comic(meta["path"])
comic = func.open_comic(meta.path)
byte_image = BytesIO(comic.getPage(page_number))
image = Image(file=byte_image)
response = make_response(image.make_blob())
response.headers["cache-control"] = "public"
date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta["path"])))
date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta.path)))
response.headers["last-modified"] = date.strftime('%a, %d %b %Y %H:%M:%S %Z')
response.headers["content-type"] = "image/" + image.format
return response
@ -167,9 +162,9 @@ def get_comic_page(comic_id, page_number):
def get_comic_thumbnail(comic_id, page_number):
meta = database.db_get_comic_by_id(comic_id)
thumb = database.db_get_thumbnail_by_id_page(comic_id, page_number)
response = make_response(thumb["image"])
response = make_response(thumb.image)
response.headers["cache-control"] = "public"
date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta["path"])))
date = pytz.utc.localize(datetime.datetime.utcfromtimestamp(os.path.getmtime(meta.path)))
response.headers["last-modified"] = date.strftime('%a, %d %b %Y %H:%M:%S %Z')
response.headers["content-type"] = thumb["type"]
response.headers["content-type"] = thumb.type
return response

View File

@ -1,10 +1,10 @@
{% for i in range(start, end) %}
<div class="col-3" style="padding: 10px">
<a href="/comics/{{ publisher_series[i]["publisher"]|urlencode }}?series={{ publisher_series[i]["series"]|urlencode }}&seriesYear={{ publisher_series[i]["seriesYear"] }}">
<a href="/comics/{{ publisher_series[i].publisher|urlencode }}?series={{ publisher_series[i].series|urlencode }}&seriesYear={{ publisher_series[i].seriesYear }}">
<div class="card">
<img class="card-img" src="/comics/get_comic/{{ publisher_series[i]['id'] }}/0/thumbnail" onerror="this.src='/static/images/default.png'">
<div class="card-body">
{{ publisher_series[i]["series"] }} {{ publisher_series[i]["seriesYear"] }}
{{ publisher_series[i].series }} {{ publisher_series[i].seriesYear }}
</div>
</div>
</a>

View File

@ -8,9 +8,9 @@
<div class="comic-grid">
{% for page_number in range(start, end) %}
<div style="margin: auto" class="comic-thumbnail card bg-dark text-white">
<a href="/comics/{{ comic["publisher"]|urlencode }}?series={{ comic["series"]|urlencode }}&seriesYear={{ comic["seriesYear"] }}&issue={{ comic["issue"]|urlencode }}&pageNumber={{ page_number }}">
<img src="/comics/get_comic/{{ comic["id"] }}/{{ page_number }}/thumbnail" alt="" style="display: inline" onerror="this.src='/static/images/default.png'">
<p class="card-text">{{ 1+page_number }}/{{ comic["pageCount"] }}</p>
<a href="/comics/{{ comic.publisher|urlencode }}?series={{ comic.series|urlencode }}&seriesYear={{ comic.seriesYear }}&issue={{ comic.issue|urlencode }}&pageNumber={{ page_number }}">
<img src="/comics/get_comic/{{ comic.id }}/{{ page_number }}/thumbnail" alt="" style="display: inline" onerror="this.src='/static/images/default.png'">
<p class="card-text">{{ 1+page_number }}/{{ comic.pageCount }}</p>
</a>
</div>
{% endfor %}

View File

@ -17,7 +17,7 @@
{% endif %}
</a>
<a href="{{ next_url }}" ondragstart="return false">
<img class="comic-page" ondragstart="return false" id="image" src="/comics/get_comic/{{ comic["id"] }}/{{ page_number }}" alt="">
<img class="comic-page" ondragstart="return false" id="image" src="/comics/get_comic/{{ comic.id }}/{{ page_number }}" alt="">
</a>
</div>

View File

@ -1,10 +1,10 @@
{% for i in range(start, end) %}
<div class="col-3" style="padding: 10px">
<a href="/comics/{{ comics[i]["publisher"]|urlencode }}?series={{ comics[i]["series"]|urlencode }}&seriesYear={{ comics[i]["seriesYear"] }}&issue={{ comics[i]["issue"]|urlencode }}">
<a href="/comics/{{ comics[i].publisher|urlencode }}?series={{ comics[i].series|urlencode }}&seriesYear={{ comics[i].seriesYear }}&issue={{ comics[i].issue|urlencode }}">
<div class="card">
<img class="card-img" src="/comics/get_comic/{{ comics[i]['id'] }}/0/thumbnail" onerror="this.src='/static/images/default.png'">
<div class="card-body">
{{ comics[i]["series"] }} {% if comics[i]["issue"] > 0 %}{{ "#{0:g}".format(comics[i]["issue"]) }}{% endif %} {% if comics[i]["title"] != None %}{{ comics[i]["title"] }} {% endif %}
{{ comics[i].series }} {% if comics[i].issue > 0 %}{{ "#{0:g}".format(comics[i].issue) }}{% endif %} {% if comics[i].title != None %}{{ comics[i].title }} {% endif %}
</div>
</div>
</a>

View File

@ -1,4 +1,4 @@
from flask import Blueprint, render_template, request, make_response, send_file, send_from_directory, current_app
from flask import Blueprint, render_template, request, make_response, send_from_directory, current_app
from flask_login import login_required
import inspect
@ -19,7 +19,6 @@ def index():
return render_template("movies/index.html", title="Movies", movies=movies, page=page, max_items=max_items, start=start, end=end, item_count=len(movies))
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(type(e), e)
return str(type(e)) + " " + str(e)
@ -40,19 +39,26 @@ def search():
return render_template("movies/search.html", title="Movies", movies=results, page=page, max_items=max_items, start=start, end=end, item_count=len(results))
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(type(e), e)
return str(type(e)) + " " + str(e)
@Movies.route("/movies/<imdb_id>")
@Movies.route("/movies/<imdb_id>", methods=["GET", "POST"])
@login_required
def movie_view(imdb_id):
try:
movie_data = database.db_get_movie_by_imdb_id(imdb_id)
return render_template("movies/movieViewer.html", title="Movies: " + movie_data["title"], movie=movie_data)
if request.method == "POST":
time = int(request.args.get("time", type=float))
length = int(request.args.get("length", type=float, default=0))
database.update_user_tv_movie_data(imdb_id, None, time, 0, True if length-time <= 15 else False)
return make_response("", 201)
else:
movie_data = database.db_get_movie_by_imdb_id(imdb_id)
user_data = database.db_get_user_tv_movie_data(movie_data.imdb_id)
if not user_data:
user_data = database.update_user_tv_movie_data(movie_data.imdb_id, None, 0, 0)
return render_template("movies/movieViewer.html", title="Movies: " + movie_data.title, movie=movie_data, user_data=user_data)
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(type(e), e)
return str(type(e)) + " " + str(e)
@ -61,10 +67,9 @@ def movie_view(imdb_id):
def movie_view_extended(imdb_id):
try:
movie_data = database.db_get_movie_by_imdb_id(imdb_id, extended=1)
return render_template("movies/movieViewer.html", title="Movies: " + movie_data["title"], movie=movie_data)
return render_template("movies/movieViewer.html", title="Movies: " + movie_data.title, movie=movie_data)
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(type(e), e)
return str(type(e)) + " " + str(e)
@ -73,10 +78,9 @@ def movie_view_extended(imdb_id):
def movie_view_directors_cut(imdb_id):
try:
movie_data = database.db_get_movie_by_imdb_id(imdb_id, directors_cut=1)
return render_template("movies/movieViewer.html", title="Movies: " + movie_data["title"], movie=movie_data)
return render_template("movies/movieViewer.html", title="Movies: " + movie_data.title, movie=movie_data)
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(type(e), e)
return str(type(e)) + " " + str(e)
@ -84,7 +88,7 @@ def movie_view_directors_cut(imdb_id):
@login_required
def get_movie(imdb_id):
movie_data = database.db_get_movie_by_imdb_id(imdb_id)
filename = movie_data["title"]+" ("+str(movie_data["year"])+").mkv"
filename = movie_data.title+" ("+str(movie_data.year)+").mkv"
response = make_response(send_from_directory(func.MOVIES_DIRECTORY, filename))
response.headers["content-type"] = "video/webm"
return response
@ -94,7 +98,7 @@ def get_movie(imdb_id):
@login_required
def get_movie_extended(imdb_id):
movie_data = database.db_get_movie_by_imdb_id(imdb_id, extended=1)
filename = movie_data["title"]+" ("+str(movie_data["year"])+").mkv"
filename = movie_data.title+" ("+str(movie_data.year)+").mkv"
response = make_response(send_from_directory(func.MOVIES_DIRECTORY, filename))
response.headers["content-type"] = "video/webm"
return response
@ -104,7 +108,7 @@ def get_movie_extended(imdb_id):
@login_required
def get_movie_directors_cut(imdb_id):
movie_data = database.db_get_movie_by_imdb_id(imdb_id, directors_cut=1)
filename = movie_data["title"]+" ("+str(movie_data["year"])+").mkv"
filename = movie_data.title+" ("+str(movie_data.year)+").mkv"
response = make_response(send_from_directory(func.MOVIES_DIRECTORY, filename))
response.headers["content-type"] = "video/webm"
return response

View File

@ -2,16 +2,17 @@
{% block content %}
<div class="container" style="text-align: center">
<video class="video-js vjs-big-play-centered" style="display: inline-block" controls preload="auto" width="1100"
poster="https://image.tmdb.org/t/p/original{{ movie["backdrop_path"] }}" data-setup="{}">
<source src="{{ url_for("movies.index") }}/get_movie/{{ movie["imdb_id"] }}{% if movie["extended"] == 1 %}/extended{% endif %}{% if movie["directors_cut"]==1 %}/directors_cut{% endif %}" type="video/webm">
<video id="player" class="video-js vjs-big-play-centered" style="display: inline-block" controls preload="auto" width="1100"
poster="https://image.tmdb.org/t/p/original{{ movie.backdrop_path }}" data-setup="{}">
<source src="{{ url_for("movies.index") }}/get_movie/{{ movie.imdb_id }}{% if movie.extended == 1 %}/extended{% endif %}{% if movie.directors_cut==1 %}/directors_cut{% endif %}" type="video/webm">
<p class='vjs-no-js'>
To view this video please enable JavaScript, and consider upgrading to a web browser that
<a href='https://videojs.com/html5-video-support/' target='_blank'>supports HTML5 video</a>
</p>
</video>
<h1>{{ movie["title"] }}</h1>
<p style="text-align: left">{{ movie["description"] }}</p>
<h1>{{ movie.title }}</h1>
<p style="text-align: left">{{ movie.description }}</p>
<a class="btn btn-primary" href="{{ url_for("movies.index") }}/get_movie/{{ movie.imdb_id }}{% if movie.extended == 1 %}/extended{% endif %}{% if movie.directors_cut==1 %}/directors_cut{% endif %}" download="{{ movie.title }}">Download</a>
</div>
{% endblock %}
@ -20,4 +21,40 @@
<img src="/static/svg/tmdb.svg" alt="" style="height: 40px;">
<p>This product uses the TMDb API but is not endorsed or certified by TMDb.</p>
</div>
<script>
videojs("player").ready(function(){
let myPlayer = videojs.getPlayer("player");
let saved_time = {{ user_data.time }};
let length = 0;
if (saved_time > 5) {
myPlayer.currentTime(saved_time);
}
function reqListener() {
console.log(this.responseText);
}
let time = myPlayer.currentTime();
let timestamp = document.getElementById("timestamp");
myPlayer.on("timeupdate", function () {
if (myPlayer.currentTime()-time >= 10) {
length = myPlayer.duration();
let oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("POST", "https://rpi.narnian.us/movies/{{ movie.imdb_id }}?time="+(time+5)+"&length="+length);
oReq.send();
time = myPlayer.currentTime();
}
});
myPlayer.on("pause", function () {
length = myPlayer.duration();
let oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("POST", "https://rpi.narnian.us/movies/{{ movie.imdb_id }}?time="+(time+5)+"&length="+length);
oReq.send();
time = myPlayer.currentTime();
});
});
</script>
{% endblock footer_content %}

View File

@ -1,10 +1,10 @@
{% for i in range(start, end) %}
<div class="col-4" style="padding: 10px">
<a href="/movies/{{ movies[i]["imdb_id"] }}{% if movies[i]["extended"] == 1 %}/extended{% endif %}{% if movies[i]["directors_cut"]==1 %}/directors_cut{% endif %}">
<a href="/movies/{{ movies[i].imdb_id }}{% if movies[i].extended == 1 %}/extended{% endif %}{% if movies[i].directors_cut==1 %}/directors_cut{% endif %}">
<div class="card">
<img class="card-img" src="https://image.tmdb.org/t/p/original{{ movies[i]["poster_path"] }}" alt="" onerror="this.src='/static/images/default.png'">
<img class="card-img" src="https://image.tmdb.org/t/p/original{{ movies[i].poster_path }}" alt="" onerror="this.src='/static/images/default.png'">
<div class="card-body">
{{ movies[i]["title"] }} ({{ movies[i]["year"] }})
{{ movies[i].title }} ({{ movies[i].year }})
</div>
</div>
</a>

View File

@ -7,5 +7,5 @@ location / {
include fastcgi_params;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param SCRIPT_NAME "";
fastcgi_pass unix:/run/matt/rpiWebApp/fcgi.sock;
fastcgi_pass unix:/run/rpiWebApp/fcgi.sock;
}

View File

@ -1,12 +1,16 @@
from flask import Flask
from flask import render_template, request, g, redirect, url_for, flash, current_app
from flask_login import LoginManager, current_user, login_user, logout_user, login_required
from flask_log import Logging
from oauthlib.oauth2 import WebApplicationClient
import threading
import logging
import inotify.adapters, inotify.constants
import inspect
import datetime
import requests
import json
import scripts.func as func
from scripts import database
@ -26,15 +30,25 @@ nullLog = NullHandler()
inotify.adapters._LOGGER = nullLog
GOOGLE_CLIENT_ID = "***REMOVED***"
GOOGLE_CLIENT_SECRET = "***REMOVED***"
GOOGLE_DISCOVERY_URL = (
"https://accounts.google.com/.well-known/openid-configuration"
)
client = WebApplicationClient(GOOGLE_CLIENT_ID)
def get_google_provider_cfg():
return requests.get(GOOGLE_DISCOVERY_URL).json()
app = Flask(__name__)
app.register_blueprint(comics.Comics)
app.register_blueprint(admin.Admin)
app.register_blueprint(movies.Movies)
app.register_blueprint(tv.TV)
app.config["SECRET_KEY"] = "***REMOVED***"
app.config["FLASK_LOG_LEVEL"] = "DEBUG"
app.config["FLASK_LOG_FACILITY"] = "daemon"
flask_log = Logging(app)
app.logger.setLevel("DEBUG")
login_manager = LoginManager(app)
login_manager.login_view = "login"
@ -99,7 +113,6 @@ def update_comic_db(sender, **kw):
database.add_comics(kw["meta"], kw["thumbnails"])
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
def update_movie_db(sender, **kw):
@ -107,17 +120,13 @@ def update_movie_db(sender, **kw):
database.add_movies(kw["movies"])
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
def update_tv_show_db(sender, **kw):
try:
database.add_tv_shows(kw["tv_show"])
if kw["tv_episodes"]:
database.add_tv_episodes(kw["tv_episodes"])
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
def update_tv_episodes_db(sender, **kw):
@ -125,7 +134,6 @@ def update_tv_episodes_db(sender, **kw):
database.add_tv_episodes(kw["tv_episodes"])
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
func.comic_loaded.connect(update_comic_db)
@ -135,26 +143,96 @@ func.tv_episodes_loaded.connect(update_tv_episodes_db)
@login_manager.user_loader
def load_user(username):
return database.get_user(username)
def load_user(email):
try:
return database.get_user(email)
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
return str(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
@app.route("/login", methods=["GET", "POST"])
def login():
try:
google_provider_cfg = get_google_provider_cfg()
authorization_endpoint = google_provider_cfg["authorization_endpoint"]
request_uri = client.prepare_request_uri(
authorization_endpoint,
redirect_uri=request.base_url + "/callback",
scope=["openid", "email", "profile"],
)
if request.method == "POST":
username = request.form.get("username")
email = request.form.get("email")
password = request.form.get("password")
user = database.get_user(username)
user = database.get_user(email)
if user is None or not user.check_password(password):
flash("invalid username or password")
flash("invalid email or password")
return redirect(url_for("login"))
login_user(user)
login_user(user, remember=True, duration=datetime.timedelta(days=7))
next_page = request.args.get("next")
if not next_page:
next_page = url_for("home")
return redirect(next_page)
return render_template("login.html", title="login")
return render_template("login.html", title="login", auth_url=request_uri)
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
return str(e)
@app.route("/login/callback")
def callback():
try:
# Get authorization code Google sent back to you
code = request.args.get("code")
google_provider_cfg = get_google_provider_cfg()
token_endpoint = google_provider_cfg["token_endpoint"]
token_url, headers, body = client.prepare_token_request(
token_endpoint,
authorization_response=request.url,
redirect_url=request.base_url,
code=code
)
token_response = requests.post(
token_url,
headers=headers,
data=body,
auth=(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET)
)
client.parse_request_body_response(json.dumps(token_response.json()))
userinfo_endpoint = google_provider_cfg["userinfo_endpoint"]
uri, headers, body = client.add_token(userinfo_endpoint)
userinfo_response = requests.get(uri, headers=headers, data=body)
if userinfo_response.json().get("email_verified"):
unique_id = userinfo_response.json()["sub"]
users_email = userinfo_response.json()["email"]
users_name = userinfo_response.json()["given_name"]
else:
return "User email not available or not verified by Google.", 400
data = (unique_id, users_name, users_email, None, False)
current_app.logger.info("user data from google: " + str(data))
user = database.get_user(users_email)
if not user:
user = database.add_user(data)
current_app.logger.info("new user: {} created".format(users_email))
current_app.logger.info("email: "+str(user.email))
current_app.logger.info("username: "+str(user.username))
current_app.logger.info("authenticated: "+str(user.is_authenticated))
current_app.logger.info("active: "+str(user.is_active))
current_app.logger.info("id: "+str(user.get_id()))
login_user(user, remember=True, duration=datetime.timedelta(days=7), force=True)
return redirect(url_for("home"))
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
return str(e)

View File

@ -1 +0,0 @@
import click

View File

@ -1,5 +1,5 @@
from flask import g, current_app
from flask_login import UserMixin
from flask_login import UserMixin, current_user
from werkzeug.security import check_password_hash
from io import BytesIO
@ -8,17 +8,19 @@ from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy import orm
from sqlalchemy import Column, Integer, String, BLOB, Boolean, Table, MetaData, DateTime
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.pool import QueuePool, NullPool
import sqlite3
import os
import inspect
import logging
from comicapi.issuestring import IssueString
from scripts import tmdb
RPI_DATABASE = "/var/lib/rpiWebApp/database.db"
RPI_IMDB_DATABASE = "/var/lib/rpiWebApp/imdb.db"
RPI_DATABASE = "/usb/storage/rpiWebApp/database.db"
RPI_IMDB_DATABASE = "/usb/storage/rpiWebApp/imdb.db"
MC_DATABASE = "***REMOVED***"
MC_IMDB_DATABASE = "C:\\Users\\Matthew\\Documents\\MyPrograms\\Websites\\rpi_web_interface\\imdb.db"
@ -27,9 +29,11 @@ DATABASE = RPI_DATABASE if os.path.exists(RPI_DATABASE) else MC_DATABASE
IMDB_DATABASE = RPI_IMDB_DATABASE if os.path.exists(RPI_IMDB_DATABASE) else MC_IMDB_DATABASE
engine = create_engine("sqlite:///"+DATABASE+"?check_same_thread=False")
Session = sessionmaker(bind=engine)
session = Session()
engine = create_engine("sqlite:///"+DATABASE+"?check_same_thread=False", poolclass=NullPool)
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
session_factory = sessionmaker(bind=engine)
Session = scoped_session(session_factory)
Base = declarative_base()
@ -84,7 +88,6 @@ class Comic(Base):
i += 1
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) +" "+ str(e))
#print(inspect.stack()[0][3], type(e), e)
def __repr__(self):
return "<Comic: {series} {issue}>".format(series=self.series, issue=self.issueText)
@ -109,7 +112,6 @@ class ComicThumbnail(Base):
i += 1
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) +" "+ str(e))
#print(inspect.stack()[0][3], type(e), e)
class Movie(Base):
@ -135,7 +137,6 @@ class Movie(Base):
i += 1
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) +" "+ str(e))
#print(inspect.stack()[0][3], type(e), e)
class TvShow(Base):
@ -157,7 +158,6 @@ class TvShow(Base):
i += 1
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
class TvEpisode(Base):
@ -181,104 +181,78 @@ class TvEpisode(Base):
i += 1
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
class User(Base, UserMixin):
__tablename__ = "users"
username = Column(String, unique=True, primary_key=True)
id = Column(String, primary_key=True)
username = Column(String)
email = Column(String, unique=True)
passwordHash = Column(String(128))
isAdmin = Column(Boolean, default=False)
def __init__(self, data):
i = 0
for column in self.__table__.columns:
setattr(self, column.name, data[i])
i += 1
self.init_user_data()
@orm.reconstructor
def init_user_data(self):
meta = MetaData()
self.comics = Table(
"comics", meta,
Column("comic_id", Integer, primary_key=True),
Column("viewed", Boolean, default=False),
Column("date", DateTime)
)
self.movies = Table(
"movies", meta,
Column("imdb_id", Integer, primary_key=True),
Column("viewed", Boolean, default=False),
Column("date", DateTime)
)
self.tv_episodes = Table(
"tv_episodes", meta,
Column("imdb_id", Integer, primary_key=True),
Column("viewed", Boolean, default=False),
Column("date", DateTime)
)
self.tv_shows = Table(
"tv_shows", meta,
Column("imdb_id", Integer, primary_key=True),
Column("viewed", Boolean, default=False),
Column("date", DateTime)
)
user_engine = create_engine("sqlite:///" + "user_" + self.username + ".db" + "?check_same_thread=False")
self.conn = engine.connect()
meta.create_all(user_engine)
def set_comic_viewed(self, comic_id, val=True):
self.conn.execute(self.comics.insert().values(comic_id=comic_id, viewed=val))
def is_comic_viewed(self, comic_id):
q = self.comics.select().where(self.comics.c.comic_id == comic_id)
result = self.conn.execute(q).fetchone()
if result:
return result["viewed"]
def set_movie_viewed(self, imdb_id, val=True):
self.conn.execute(self.movies.insert().values(comic_id=imdb_id, viewed=val))
def is_movie_viewed(self, imdb_id):
q = self.movies.select().where(self.movies.c.imdb_id == imdb_id)
result = self.conn.execute(q).fetchone()
if result:
return result["viewed"]
def set_tv_episode_viewed(self, imdb_id, val=True):
self.conn.execute(self.tv_episodes.insert().values(comic_id=imdb_id, viewed=val))
def is_tv_episode_viewed(self, imdb_id):
q = self.tv_episodes.select().where(self.tv_episodes.c.imdb_id == imdb_id)
result = self.conn.execute(q).fetchone()
if result:
return result["viewed"]
def set_tv_show_viewed(self, imdb_id, val=True):
self.conn.execute(self.tv_shows.insert().values(comic_id=imdb_id, viewed=val))
def is_tv_show_viewed(self, imdb_id):
q = self.tv_shows.select().where(self.tv_shows.c.imdb_id == imdb_id)
result = self.conn.execute(q).fetchone()
if result:
return result["viewed"]
try:
for column in self.__table__.columns:
setattr(self, column.name, data[i])
i += 1
pass
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
def get_id(self):
return self.username
return self.email
def check_password(self, password):
if not self.passwordHash:
return
result = check_password_hash(self.passwordHash, password)
return result
class UserTvMovieData(Base):
__tablename__ = "user_tv_movie_data"
id = Column(Integer, primary_key=True, autoincrement=True)
user = Column(String)
imdb_id = Column(String)
parent_imdb_id = Column(String)
time = Column(Integer)
length = Column(Integer)
finished = Column(Boolean, default=False)
def __init__(self, data):
i = 0
try:
for column in self.__table__.columns:
if column.name == "id":
continue
setattr(self, column.name, data[i])
i += 1
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
"""class UserComicData(Base):
__tablename__ = "user_comic_data"
id = Column(Integer, primary_key=True, autoincrement=True)
user = Column(String)
comic_id = Column(Integer)
viewed = Column(Boolean, default=False)
def __init__(self, data):
i = 0
try:
for column in self.__table__.columns:
setattr(self, column.name, data[i])
i += 1
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))"""
def get_db():
db = getattr(g, '_database', None)
if db is None:
@ -336,22 +310,24 @@ def initialize_db():
"teams" TEXT,
"locations" TEXT,
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE
)""")
)""")
get_db().execute("""CREATE TABLE IF NOT EXISTS "credits" (
"id" INTEGER NOT NULL,
"role" TEXT,
"name" TEXT,
"primary" INTEGER NOT NULL
)""")
)""")
get_db().execute("""CREATE TABLE IF NOT EXISTS "comic_thumbnails" (
"comic_id" INTEGER,
"pageNumber" INTEGER,
"image" BLOB,
"type" TEXT,
"id" INTEGER PRIMARY KEY AUTOINCREMENT
)""")
)""")
get_db().execute("""CREATE TABLE IF NOT EXISTS "users" (
"username" TEXT UNIQUE PRIMARY KEY,
"id" TEXT PRIMARY KEY,
"username" TEXT,
"email" TEXT UNIQUE,
"passwordHash" VARCHAR(128),
"isAdmin" INTEGER NOT NULL DEFAULT 0
)""")
@ -367,7 +343,7 @@ def initialize_db():
"directors_cut" INTEGER,
"poster_path" TEXT,
"backdrop_path" TEXT
)""")
)""")
get_db().execute("""CREATE TABLE IF NOT EXISTS "tv_shows" (
"imdb_id" TEXT PRIMARY KEY ,
"tmdb_id" INTEGER UNIQUE,
@ -387,7 +363,16 @@ def initialize_db():
"description" TEXT,
"still_path" TEXT,
"path" TEXT
)""")
)""")
get_db().execute("""CREATE TABLE IF NOT EXISTS "user_tv_movie_data" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
"user" TEXT,
"imdb_id" TEXT,
"parent_imdb_id" TEXT,
"time" INTEGER,
"length" INTEGER,
"finished" INTEGER DEFAULT 0
)""")
get_db().execute("CREATE INDEX IF NOT EXISTS path_index ON comics(path);")
get_db().execute("CREATE INDEX IF NOT EXISTS comic_id_index ON comic_thumbnails(comic_id);")
get_db().commit()
@ -398,7 +383,34 @@ def initialize_db():
get_imdb().commit()
def update_user_tv_movie_data(imdb_id, parent_id, time, length, finished=False):
session = Session()
email = current_user.email
user_data = session.query(UserTvMovieData).filter(UserTvMovieData.imdb_id == imdb_id, UserTvMovieData.user == email).one_or_none()
if user_data:
user_data.time = time
user_data.finished = finished
if not user_data.length > 0 and length > 0:
user_data.length = length
session.commit()
return user_data
else:
data = UserTvMovieData((email, imdb_id, parent_id, time, length, finished))
session.add(data)
session.commit()
return data
def add_user(data):
session = Session()
user = User(data)
session.add(user)
session.commit()
return user
def add_movies(movies):
session = Session()
for movie_data in movies:
movie = Movie(movie_data)
session.add(movie)
@ -407,15 +419,16 @@ def add_movies(movies):
def add_tv_shows(tv_show_data):
try:
session = Session()
tv_show = TvShow(tv_show_data)
session.add(tv_show)
session.commit()
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
def add_tv_episodes(episodes):
session = Session()
for episode_data in episodes:
try:
episode = TvEpisode(episode_data)
@ -424,10 +437,10 @@ def add_tv_episodes(episodes):
session.commit()
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
def add_comics(meta, thumbnails):
session = Session()
data = []
for info in meta:
issue = IssueString(info[1].issue).asFloat()
@ -458,93 +471,96 @@ def add_comics(meta, thumbnails):
def db_get_all_comics():
session = Session()
result = session.query(Comic).all()
return result
def db_get_series_by_publisher(publisher):
session = Session()
result = session.query(Comic).filter(Comic.publisher == publisher).group_by(Comic.publisher, Comic.series, Comic.seriesYear).order_by(Comic.series, Comic.seriesYear).all()
series = [comic.__dict__ for comic in result]
series = result
return series
def db_get_comics_in_series(series, publisher, series_year):
session = Session()
result = session.query(Comic).filter(Comic.publisher == publisher, Comic.series == series, Comic.seriesYear == series_year)\
.order_by(Comic.issue).all()
comics = [comic.__dict__ for comic in result]
comics = result
return comics
def get_publishers():
session = Session()
result = session.query(Comic.publisher).distinct(Comic.publisher).order_by(Comic.publisher).all()
publishers = [r for (r,) in result]
return publishers
def db_get_comic_by_id(comic_id):
comic = session.query(Comic).filter(Comic.id == comic_id).one().__dict__
session = Session()
comic = session.query(Comic).filter(Comic.id == comic_id).one_or_none()
return comic
def db_get_thumbnail_by_id_page(comic_id, pageNumber):
thumbnail = session.query(ComicThumbnail).filter(ComicThumbnail.comic_id == comic_id, ComicThumbnail.pageNumber == pageNumber).one().__dict__
session = Session()
thumbnail = session.query(ComicThumbnail).filter(ComicThumbnail.comic_id == comic_id, ComicThumbnail.pageNumber == pageNumber).one_or_none()
return thumbnail
def db_get_comic(publisher, series, series_year, issue):
comic = session.query(Comic).filter(Comic.publisher == publisher, Comic.series == series, Comic.seriesYear == series_year, Comic.issue == issue).one().__dict__
session = Session()
comic = session.query(Comic).filter(Comic.publisher == publisher, Comic.series == series, Comic.seriesYear == series_year, Comic.issue == issue).one_or_none()
return comic
def comic_path_in_db(path):
try:
session = Session()
result = session.query(Comic).filter(Comic.path == path).one_or_none()
if result:
return True
except Exception as e:
# print(path)
current_app.logger.info(path)
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
return False
def movie_path_in_db(path):
try:
session = Session()
result = session.query(Movie).filter(Movie.path == path).one_or_none()
if result:
return True
except Exception as e:
# print(path)
current_app.logger.info(path)
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
return False
def tv_show_path_in_db(path):
try:
session = Session()
result = session.query(TvShow).filter(TvShow.path == path).one_or_none()
if result:
return True
except Exception as e:
# print(path)
current_app.logger.info(path)
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
return False
def tv_episode_path_in_db(path):
try:
session = Session()
result = session.query(TvEpisode).filter(TvEpisode.path == path).one_or_none()
if result:
return True
except Exception as e:
# print(path)
current_app.logger.info(path)
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
return False
@ -603,32 +619,53 @@ def tmdb_get_tv_episode_by_imdb_id(imdb_id):
def db_get_all_movies():
session = Session()
movies = session.query(Movie).order_by(Movie.title, Movie.year).all()
return movies
def db_get_movie_by_imdb_id(imdb_id, extended=False, directors_cut=False):
session = Session()
result = session.query(Movie).filter(Movie.imdb_id == imdb_id, Movie.extended == extended,
Movie.directors_cut == directors_cut).one()
Movie.directors_cut == directors_cut).one_or_none()
return result
def get_all_tv_shows():
session = Session()
result = session.query(TvShow).order_by(TvShow.title, TvShow.year).all()
return result
def get_tv_show_episodes_by_imdb_id(imdb_id):
session = Session()
result = session.query(TvEpisode).filter(TvEpisode.parent_imdb_id == imdb_id).order_by(TvEpisode.season, TvEpisode.episode).all()
return result
def db_get_episode_by_imdb_id(imdb_id):
result = session.query(TvEpisode).filter(TvEpisode.imdb_id == imdb_id).one()
session = Session()
result = session.query(TvEpisode).filter(TvEpisode.imdb_id == imdb_id).one_or_none()
return result
def db_get_user_tv_movie_data(imdb_id):
session = Session()
email = current_user.email
result = session.query(UserTvMovieData).filter(UserTvMovieData.user == email, UserTvMovieData.imdb_id == imdb_id).one_or_none()
return result
def db_get_user_tv_show_episodes_data(parent_imdb_id) -> list:
session = Session()
email = current_user.email
result = session.query(UserTvMovieData).filter(UserTvMovieData.user == email,
UserTvMovieData.parent_imdb_id == parent_imdb_id).all()
return result
def db_search_table_columns_by_query(query, table, columns, group=[], order=[]):
session = Session()
results = {}
final_query = "%" + query.replace(" ", "%") + "%"
for column in columns:
@ -644,22 +681,18 @@ def db_search_comics(query):
results = db_search_table_columns_by_query(query, Comic, [Comic.publisher, Comic.title, Comic.series, Comic.year])
series_results = db_search_table_columns_by_query(query, Comic, [Comic.publisher, Comic.title, Comic.series, Comic.year],
group=[Comic.series, Comic.seriesYear], order=[Comic.issue])
for row in results["publisher"]:
if row["publisher"] not in publishers:
publishers.append(row.publisher)
for row in series_results["series"]:
dict = row.__dict__
if dict not in series:
series.append(dict)
if row not in series:
series.append(row)
for row in results["title"]:
dict = row.__dict__
if dict not in comics:
comics.append(dict)
if row not in comics:
comics.append(row)
for row in results["year"]:
dict = row.__dict__
if dict not in comics:
comics.append(dict)
if row not in comics:
comics.append(row)
return {"publisher": publishers, "series": series, "comics": comics}
@ -668,14 +701,14 @@ def db_search_movies(query):
results = db_search_table_columns_by_query(query, Movie, [Movie.title, Movie.year, Movie.description], order=[Movie.title])
movies = []
for movie in results["title"]:
if movie.__dict__ not in movies:
movies.append(movie.__dict__)
if movie not in movies:
movies.append(movie)
for movie in results["description"]:
if movie.__dict__ not in movies:
movies.append(movie.__dict__)
if movie not in movies:
movies.append(movie)
for movie in results["year"]:
if movie.__dict__ not in movies:
movies.append(movie.__dict__)
if movie not in movies:
movies.append(movie)
return movies
@ -683,14 +716,14 @@ def db_search_tv_shows(query):
results = db_search_table_columns_by_query(query, TvShow, [TvShow.title, TvShow.year, TvShow.description], order=[TvShow.title])
tv_shows = []
for show in results["title"]:
if show.__dict__ not in tv_shows:
tv_shows.append(show.__dict__)
if show not in tv_shows:
tv_shows.append(show)
for show in results["description"]:
if show.__dict__ not in tv_shows:
tv_shows.append(show.__dict__)
if show not in tv_shows:
tv_shows.append(show)
for show in results["year"]:
if show.__dict__ not in tv_shows:
tv_shows.append(show.__dict__)
if show not in tv_shows:
tv_shows.append(show)
return tv_shows
@ -725,8 +758,12 @@ def fix_thumbnails():
print("Finished fix thumbnail size")
def get_user(username):
result = session.query(User).filter(User.username == username).one()
if result:
return result
return None
def get_user(email):
try:
session = Session()
result = session.query(User).filter(User.email == email).one_or_none()
if result:
return result
return None
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))

View File

@ -3,7 +3,7 @@ from wand.image import Image
import sqlite3, os
RPI_DATABASE = "/var/lib/rpiWebApp/database.db"
RPI_DATABASE = "/usb/storage/rpiWebApp/database.db"
MC_DATABASE = "***REMOVED***"

View File

@ -1,4 +1,5 @@
from flask import current_app
from flask import g
from comicapi import comicarchive
from blinker import Namespace
@ -54,18 +55,20 @@ def get_comics():
test_path = path.encode("utf8")
except Exception as e:
current_app.logger.info("encoding failed on: "+path)
# print("encoding failed on:", path)
continue
archive = open_comic(path)
md = archive.readCIX()
if md.publisher in publishers_to_ignore:
continue
current_app.logger.info(path)
# print(path)
meta.append((path, md))
thumbnails.append(get_comic_thumbnails(archive))
comics_added += 1
i += 1
try:
meta.append((path, md))
thumbnails.append(get_comic_thumbnails(archive))
comics_added += 1
i += 1
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
continue
if i >= 2:
comic_loaded.send("anonymous", meta=meta.copy(), thumbnails=thumbnails.copy())
meta.clear()
@ -75,9 +78,6 @@ def get_comics():
current_app.logger.info("total number of comics: "+str(total_comics))
current_app.logger.info("comics in database: "+str(comics_in_db))
current_app.logger.info("number of comics added: "+str(comics_added))
# print("total number of comics:", total_comics)
# print("comics in database:", comics_in_db)
# print("number of comics added:", comics_added)
comic_loaded.send("anonymous", meta=meta, thumbnails=thumbnails)
@ -90,16 +90,18 @@ def get_comic(path):
test_path = path.encode("utf8")
except Exception as e:
current_app.logger.info("encoding failed on: "+path)
# print("encoding failed on:", path)
return
archive = open_comic(path)
md = archive.readCIX()
if md.publisher in publishers_to_ignore:
return
current_app.logger.info(path)
# print(path)
meta.append((path, md))
thumbnails.append(get_comic_thumbnails(archive))
try:
thumbnails.append(get_comic_thumbnails(archive))
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
return
comic_loaded.send("anonymous", meta=meta, thumbnails=thumbnails)
@ -131,7 +133,6 @@ def open_comic(path):
def get_movies():
current_app.logger.info("start load movies")
# print("start load movies")
pattern = r"(.+) \((....)\)(\(extended\))?( Director's Cut)?(\.mkv)"
movies = []
total_movies = 0
@ -147,13 +148,10 @@ def get_movies():
match = re.fullmatch(pattern, f)
if not match:
current_app.logger.info(f+" did not match regex.")
# print(f, "did not match regex.")
continue
current_app.logger.info("movie path: "+path)
# print("movie path:", path)
title = match.group(1)
current_app.logger.info("movie title: "+title)
# print("movie title:", title)
year = int(match.group(2))
extended = False
directors_cut = False
@ -167,7 +165,6 @@ def get_movies():
imdb_data = database.imdb_get_movie(title, year)
if not imdb_data:
current_app.logger.info("could not get imdb data for: "+title+" "+str(year))
# print("could not get imdb data for:", title, year)
continue
imdb_id = imdb_data["tconst"]
length = imdb_data["runtimeMinutes"]
@ -175,7 +172,6 @@ def get_movies():
tmdb_data = database.tmdb_get_movie_by_imdb_id(imdb_id)
if not tmdb_data:
current_app.logger.info("could not get tmdb data")
# print("could not get tmdb data")
continue
tmdb_id = tmdb_data[0]
description = tmdb_data[1]
@ -196,10 +192,6 @@ def get_movies():
current_app.logger.info("total movies: "+str(total_movies))
current_app.logger.info("movies in database: "+str(movies_in_db))
current_app.logger.info("movies added: "+str(movies_added))
# print("finish load movies")
# print("total movies:", total_movies)
# print("movies in database:", movies_in_db)
# print("movies added:", movies_added)
def get_tv_shows():
@ -221,7 +213,7 @@ def get_tv_shows():
if not tmdb_data:
current_app.logger.info("could not get tmdb data for:" + series_name + " " + str(series_year))
# print("could not get tmdb data for:", series_name, series_year)
with open("/var/lib/rpiWebApp/log.txt", "a") as f:
with open("/usb/storage/rpiWebApp/log.txt", "a") as f:
f.write("could not get tmdb data for: " + imdb_id + " " + series_name + " " + str(series_year)+"\n")
continue
tmdb_id = tmdb_data[0]
@ -230,41 +222,42 @@ def get_tv_shows():
tv_show_data = (imdb_id, tmdb_id, series_name, series_year, description, poster_path, path)
tv_show_loaded.send("anonymous", tv_show=tv_show_data)
current_app.logger.info("finished load tv shows.")
# print("finished load tv shows.")
def get_tv_episodes():
video_pattern = r"S(..)E(..) - (.+)(.mp4|.mkv)"
rows = database.get_all_tv_shows()
for tv_show in rows:
episodes = []
for video in sorted(os.listdir(tv_show.path)):
video_match = re.fullmatch(video_pattern, video)
if video_match:
path = tv_show.path + video
if not database.tv_episode_path_in_db(path):
season = int(video_match.group(1))
episode = int(video_match.group(2))
episode_name = video_match.group(3)
episode_imdb_data = database.imdb_get_tv_episode(tv_show.imdb_id, season, episode)
if not episode_imdb_data:
current_app.logger.info("could not get imdb data for: "+tv_show.title+" "+str(tv_show.year)+" "+str(season)+" "+str(episode))
print("could not get imdb data for:", tv_show.title, tv_show.year, season, episode)
continue
episode_imdb_id = episode_imdb_data["tconst"]
episode_tmdb_data = database.tmdb_get_tv_episode_by_imdb_id(episode_imdb_id)
if not episode_tmdb_data:
current_app.logger.info("could not get tmdb data for: "+tv_show.title+" "+str(tv_show.year)+" "+str(season)+" "+str(episode))
# print("could not get tmdb data for:", tv_show.title, tv_show.year, season, episode)
with open("/var/lib/rpiWebApp/log.txt", "a") as f:
f.write("could not get tmdb data for: " + episode_imdb_id + " " + tv_show.title + " " + str(
tv_show.year) + " " + str(season) + " " + str(episode) + "\n")
continue
episode_tmdb_id = episode_tmdb_data[0]
episode_description = episode_tmdb_data[1]
episode_still_path = episode_tmdb_data[2]
episodes.append((episode_imdb_id, tv_show.imdb_id, episode_tmdb_id, episode_name, season, episode,
episode_description, episode_still_path, path))
tv_episodes_loaded.send("anonymous", tv_episodes=episodes)
current_app.logger.info("finished load tv episodes.")
# print("finish load tv episodes.")
try:
video_pattern = r"S(\d+)E(\d+) - (.+)(.mp4|.mkv)"
rows = database.get_all_tv_shows()
for tv_show in rows:
episodes = []
for video in sorted(os.listdir(tv_show.path)):
video_match = re.fullmatch(video_pattern, video)
if video_match:
path = tv_show.path + video
if not database.tv_episode_path_in_db(path):
season = int(video_match.group(1))
episode = int(video_match.group(2))
episode_name = video_match.group(3)
episode_imdb_data = database.imdb_get_tv_episode(tv_show.imdb_id, season, episode)
if not episode_imdb_data:
current_app.logger.info("could not get imdb data for: "+tv_show.title+" "+str(tv_show.year)+" "+str(season)+" "+str(episode))
print("could not get imdb data for:", tv_show.title, tv_show.year, season, episode)
continue
episode_imdb_id = episode_imdb_data["tconst"]
episode_tmdb_data = database.tmdb_get_tv_episode_by_imdb_id(episode_imdb_id)
if not episode_tmdb_data:
current_app.logger.info("could not get tmdb data for: "+tv_show.title+" "+str(tv_show.year)+" "+str(season)+" "+str(episode))
with open("/usb/storage/rpiWebApp/log.txt", "w") as f:
f.write("could not get tmdb data for: " + episode_imdb_id + " " + tv_show.title + " " + str(
tv_show.year) + " " + str(season) + " " + str(episode) + "\n")
continue
episode_tmdb_id = episode_tmdb_data[0]
episode_description = episode_tmdb_data[1]
episode_still_path = episode_tmdb_data[2]
episodes.append((episode_imdb_id, tv_show.imdb_id, episode_tmdb_id, episode_name, season, episode,
episode_description, episode_still_path, path))
tv_episodes_loaded.send("anonymous", tv_episodes=episodes)
current_app.logger.info("finished load tv episodes.")
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))

View File

@ -1,12 +1,12 @@
import sqlite3, subprocess, os
RPI_IMDB_DATABASE = "/var/lib/rpiWebApp/"
RPI_IMDB_DATABASE = "/usb/storage/rpiWebApp/"
RPI_TSV_DIRECTORY = "/usb/storage/imdb-rename/"
RPI_CSV_DIRECTORY = "/home/matt/"
MC_IMDB_DATABASE = "C:\\Users\\Matthew\\Documents\\MyPrograms\\Websites\\rpi_web_interface\\"
MC_TSV_DIRECTORY = "C:\\\\Users\\\\Matthew\\\\Documents\\\\IMDB\\\\"
MC_CSV_DIRECTORY = "C:\\\\Users\\\\Matthew\\\\Documents\\\\IMDB\\\\"
MC_IMDB_DATABASE = "***REMOVED***"
MC_TSV_DIRECTORY = "***REMOVED***"
MC_CSV_DIRECTORY = "***REMOVED***"
IMDB_DATABASE = RPI_IMDB_DATABASE if os.path.exists(RPI_IMDB_DATABASE) else MC_IMDB_DATABASE

View File

@ -1,9 +1,6 @@
from flask import current_app
import requests
import logging
import inspect
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)
API_KEY = "***REMOVED***"
TMDB_FIND_URL = "https://api.themoviedb.org/3/find/"
@ -18,18 +15,15 @@ def get_movie_data(imdb_id):
"language": "en-US",
"external_source": "imdb_id"
}
r = requests.get(TMDB_FIND_URL+imdb_id, data=data)
r = requests.get(TMDB_FIND_URL+imdb_id, params=data)
info = dict(r.json())
if "status_code" in info.keys():
current_app.logger.info("error getting tmdb data, status code: "+str(info["status_code"]))
# print("error getting tmdb data, status code:", info["status_code"])
current_app.logger.info("error getting tmdb movie data, status code: "+str(info["status_code"])+" "+str(info["status_message"]))
return None
if info["movie_results"] == []:
current_app.logger.info("no tmdb results for: " + str(imdb_id))
# print("no tmdb results for:", imdb_id)
return None
current_app.logger.info("tmdb movie title: " + str(info["movie_results"][0]["title"]))
# print("tmdb movie title:", info["movie_results"][0]["title"])
movie_id = info["movie_results"][0]["id"]
overview = info["movie_results"][0]["overview"]
poster_path = info["movie_results"][0]["poster_path"]
@ -38,7 +32,6 @@ def get_movie_data(imdb_id):
return movie_id, overview, poster_path, backdrop_path
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
def get_tv_show_data(imdb_id):
@ -48,18 +41,15 @@ def get_tv_show_data(imdb_id):
"language": "en-US",
"external_source": "imdb_id"
}
r = requests.get(TMDB_FIND_URL+imdb_id, data=data)
r = requests.get(TMDB_FIND_URL+imdb_id, params=data)
info = dict(r.json())
if "status_code" in info.keys():
current_app.logger.info("error getting tmdb data, status code: " + str(info["status_code"]))
# print("error getting tmdb data, status code:", info["status_code"])
current_app.logger.info("error getting tmdb tv show data, status code: " + str(info["status_code"])+" "+str(info["status_message"]))
return None
if info["tv_results"] == []:
current_app.logger.info("no tmdb results for: " + str(imdb_id))
# print("no tmdb results for:", imdb_id)
return None
current_app.logger.info("tmdb tv show title: " + str(info["tv_results"][0]["name"]))
# print("tmdb tv show title:", info["tv_results"][0]["name"])
tv_show_id = info["tv_results"][0]["id"]
overview = info["tv_results"][0]["overview"]
poster_path = info["tv_results"][0]["poster_path"]
@ -67,7 +57,6 @@ def get_tv_show_data(imdb_id):
return tv_show_id, overview, poster_path
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)
def get_tv_episode_data(imdb_id):
@ -77,18 +66,15 @@ def get_tv_episode_data(imdb_id):
"language": "en-US",
"external_source": "imdb_id"
}
r = requests.get(TMDB_FIND_URL+imdb_id, data=data)
r = requests.get(TMDB_FIND_URL+imdb_id, params=data)
info = dict(r.json())
if "status_code" in info.keys():
current_app.logger.info("error getting tmdb data, status code: " + str(info["status_code"]))
# print("error getting tmdb data, status code:", info["status_code"])
current_app.logger.info("error getting tmdb tv episode data, status code: " + str(info["status_code"])+" "+str(info["status_message"]))
return None
if info["tv_episode_results"] == []:
current_app.logger.info("no tmdb results for: " + str(imdb_id))
# print("no tmdb results for:", imdb_id)
return None
current_app.logger.info("tmdb tv_episode title: " + str(info["tv_episode_results"][0]["name"]))
# print("tmdb tv episode title:", info["tv_episode_results"][0]["name"])
tv_episode_id = info["tv_episode_results"][0]["id"]
name = info["tv_episode_results"][0]["name"]
overview = info["tv_episode_results"][0]["overview"]
@ -97,4 +83,3 @@ def get_tv_episode_data(imdb_id):
return tv_episode_id, overview, still_path
except Exception as e:
current_app.logger.info(inspect.stack()[0][3] + " " + str(type(e)) + " " + str(e))
# print(inspect.stack()[0][3], type(e), e)

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
static/images/DC.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
static/images/Epic.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
static/images/Gold Key.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
static/images/Image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
static/images/Marvel UK.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
static/images/Yen Press.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,34 +0,0 @@
videojs.addLanguage('ar', {
"Play": "تشغيل",
"Pause": "إيقاف",
"Current Time": "الوقت الحالي",
"Duration": "مدة",
"Remaining Time": "الوقت المتبقي",
"Stream Type": "نوع التيار",
"LIVE": "مباشر",
"Loaded": "تم التحميل",
"Progress": "التقدم",
"Fullscreen": "ملء الشاشة",
"Non-Fullscreen": "تعطيل ملء الشاشة",
"Mute": "صامت",
"Unmute": "غير الصامت",
"Playback Rate": "معدل التشغيل",
"Subtitles": "الترجمة",
"subtitles off": "إيقاف الترجمة",
"Captions": "التعليقات",
"captions off": "إيقاف التعليقات",
"Chapters": "فصول",
"You aborted the media playback": "لقد ألغيت تشغيل الفيديو",
"A network error caused the media download to fail part-way.": "تسبب خطأ في الشبكة بفشل تحميل الفيديو بالكامل.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "لا يمكن تحميل الفيديو بسبب فشل في الخادوم أو الشبكة ، أو فشل بسبب عدم إمكانية قراءة تنسيق الفيديو.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "تم إيقاف تشغيل الفيديو بسبب مشكلة فساد أو لأن الفيديو المستخدم يستخدم ميزات غير مدعومة من متصفحك.",
"No compatible source was found for this media.": "فشل العثور على أي مصدر متوافق مع هذا الفيديو.",
"Play Video": "تشغيل الفيديو",
"Close": "أغلق",
"Modal Window": "نافذة مشروطة",
"This is a modal window": "هذه نافذة مشروطة",
"This modal can be closed by pressing the Escape key or activating the close button.": "يمكن غلق هذه النافذة المشروطة عن طريق الضغط على زر الخروج أو تفعيل زر الإغلاق",
", opens captions settings dialog": ", تفتح نافذة خيارات التعليقات",
", opens subtitles settings dialog": ", تفتح نافذة خيارات الترجمة",
", selected": ", مختار"
});

View File

@ -1,34 +0,0 @@
{
"Play": "تشغيل",
"Pause": "إيقاف",
"Current Time": "الوقت الحالي",
"Duration": "مدة",
"Remaining Time": "الوقت المتبقي",
"Stream Type": "نوع التيار",
"LIVE": "مباشر",
"Loaded": "تم التحميل",
"Progress": "التقدم",
"Fullscreen": "ملء الشاشة",
"Non-Fullscreen": "تعطيل ملء الشاشة",
"Mute": "صامت",
"Unmute": "غير الصامت",
"Playback Rate": "معدل التشغيل",
"Subtitles": "الترجمة",
"subtitles off": "إيقاف الترجمة",
"Captions": "التعليقات",
"captions off": "إيقاف التعليقات",
"Chapters": "فصول",
"You aborted the media playback": "لقد ألغيت تشغيل الفيديو",
"A network error caused the media download to fail part-way.": "تسبب خطأ في الشبكة بفشل تحميل الفيديو بالكامل.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "لا يمكن تحميل الفيديو بسبب فشل في الخادوم أو الشبكة ، أو فشل بسبب عدم إمكانية قراءة تنسيق الفيديو.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "تم إيقاف تشغيل الفيديو بسبب مشكلة فساد أو لأن الفيديو المستخدم يستخدم ميزات غير مدعومة من متصفحك.",
"No compatible source was found for this media.": "فشل العثور على أي مصدر متوافق مع هذا الفيديو.",
"Play Video": "تشغيل الفيديو",
"Close": "أغلق",
"Modal Window": "نافذة مشروطة",
"This is a modal window": "هذه نافذة مشروطة",
"This modal can be closed by pressing the Escape key or activating the close button.": "يمكن غلق هذه النافذة المشروطة عن طريق الضغط على زر الخروج أو تفعيل زر الإغلاق",
", opens captions settings dialog": ", تفتح نافذة خيارات التعليقات",
", opens subtitles settings dialog": ", تفتح نافذة خيارات الترجمة",
", selected": ", مختار"
}

View File

@ -1,27 +0,0 @@
videojs.addLanguage('es', {
"Play": "Reproducción",
"Play Video": "Reproducción Vídeo",
"Pause": "Pausa",
"Current Time": "Tiempo reproducido",
"Duration": "Duración total",
"Remaining Time": "Tiempo restante",
"Stream Type": "Tipo de secuencia",
"LIVE": "DIRECTO",
"Loaded": "Cargado",
"Progress": "Progreso",
"Fullscreen": "Pantalla completa",
"Non-Fullscreen": "Pantalla no completa",
"Mute": "Silenciar",
"Unmute": "No silenciado",
"Playback Rate": "Velocidad de reproducción",
"Subtitles": "Subtítulos",
"subtitles off": "Subtítulos desactivados",
"Captions": "Subtítulos especiales",
"captions off": "Subtítulos especiales desactivados",
"Chapters": "Capítulos",
"You aborted the media playback": "Ha interrumpido la reproducción del vídeo.",
"A network error caused the media download to fail part-way.": "Un error de red ha interrumpido la descarga del vídeo.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "No se ha podido cargar el vídeo debido a un fallo de red o del servidor o porque el formato es incompatible.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "La reproducción de vídeo se ha interrumpido por un problema de corrupción de datos o porque el vídeo precisa funciones que su navegador no ofrece.",
"No compatible source was found for this media.": "No se ha encontrado ninguna fuente compatible con este vídeo."
});

View File

@ -1,27 +0,0 @@
{
"Play": "Reproducción",
"Play Video": "Reproducción Vídeo",
"Pause": "Pausa",
"Current Time": "Tiempo reproducido",
"Duration": "Duración total",
"Remaining Time": "Tiempo restante",
"Stream Type": "Tipo de secuencia",
"LIVE": "DIRECTO",
"Loaded": "Cargado",
"Progress": "Progreso",
"Fullscreen": "Pantalla completa",
"Non-Fullscreen": "Pantalla no completa",
"Mute": "Silenciar",
"Unmute": "No silenciado",
"Playback Rate": "Velocidad de reproducción",
"Subtitles": "Subtítulos",
"subtitles off": "Subtítulos desactivados",
"Captions": "Subtítulos especiales",
"captions off": "Subtítulos especiales desactivados",
"Chapters": "Capítulos",
"You aborted the media playback": "Ha interrumpido la reproducción del vídeo.",
"A network error caused the media download to fail part-way.": "Un error de red ha interrumpido la descarga del vídeo.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "No se ha podido cargar el vídeo debido a un fallo de red o del servidor o porque el formato es incompatible.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "La reproducción de vídeo se ha interrumpido por un problema de corrupción de datos o porque el vídeo precisa funciones que su navegador no ofrece.",
"No compatible source was found for this media.": "No se ha encontrado ninguna fuente compatible con este vídeo."
}

View File

@ -1,26 +0,0 @@
videojs.addLanguage('nb', {
"Play": "Spill",
"Pause": "Pause",
"Current Time": "Aktuell tid",
"Duration": "Varighet",
"Remaining Time": "Gjenstående tid",
"Stream Type": "Type strøm",
"LIVE": "DIREKTE",
"Loaded": "Lastet inn",
"Progress": "Status",
"Fullscreen": "Fullskjerm",
"Non-Fullscreen": "Lukk fullskjerm",
"Mute": "Lyd av",
"Unmute": "Lyd på",
"Playback Rate": "Avspillingsrate",
"Subtitles": "Undertekst på",
"subtitles off": "Undertekst av",
"Captions": "Undertekst for hørselshemmede på",
"captions off": "Undertekst for hørselshemmede av",
"Chapters": "Kapitler",
"You aborted the media playback": "Du avbrøt avspillingen.",
"A network error caused the media download to fail part-way.": "En nettverksfeil avbrøt nedlasting av videoen.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Videoen kunne ikke lastes ned, på grunn av nettverksfeil eller serverfeil, eller fordi formatet ikke er støttet.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Videoavspillingen ble avbrudt på grunn av ødelagte data eller fordi videoen ville gjøre noe som nettleseren din ikke har støtte for.",
"No compatible source was found for this media.": "Fant ikke en kompatibel kilde for dette mediainnholdet."
});

View File

@ -1,26 +0,0 @@
{
"Play": "Spill",
"Pause": "Pause",
"Current Time": "Aktuell tid",
"Duration": "Varighet",
"Remaining Time": "Gjenstående tid",
"Stream Type": "Type strøm",
"LIVE": "DIREKTE",
"Loaded": "Lastet inn",
"Progress": "Status",
"Fullscreen": "Fullskjerm",
"Non-Fullscreen": "Lukk fullskjerm",
"Mute": "Lyd av",
"Unmute": "Lyd på",
"Playback Rate": "Avspillingsrate",
"Subtitles": "Undertekst på",
"subtitles off": "Undertekst av",
"Captions": "Undertekst for hørselshemmede på",
"captions off": "Undertekst for hørselshemmede av",
"Chapters": "Kapitler",
"You aborted the media playback": "Du avbrøt avspillingen.",
"A network error caused the media download to fail part-way.": "En nettverksfeil avbrøt nedlasting av videoen.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Videoen kunne ikke lastes ned, på grunn av nettverksfeil eller serverfeil, eller fordi formatet ikke er støttet.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Videoavspillingen ble avbrudt på grunn av ødelagte data eller fordi videoen ville gjøre noe som nettleseren din ikke har støtte for.",
"No compatible source was found for this media.": "Fant ikke en kompatibel kilde for dette mediainnholdet."
}

View File

@ -1,26 +0,0 @@
videojs.addLanguage('nn', {
"Play": "Spel",
"Pause": "Pause",
"Current Time": "Aktuell tid",
"Duration": "Varigheit",
"Remaining Time": "Tid attende",
"Stream Type": "Type straum",
"LIVE": "DIREKTE",
"Loaded": "Lasta inn",
"Progress": "Status",
"Fullscreen": "Fullskjerm",
"Non-Fullscreen": "Stenga fullskjerm",
"Mute": "Ljod av",
"Unmute": "Ljod på",
"Playback Rate": "Avspelingsrate",
"Subtitles": "Teksting på",
"subtitles off": "Teksting av",
"Captions": "Teksting for høyrselshemma på",
"captions off": "Teksting for høyrselshemma av",
"Chapters": "Kapitel",
"You aborted the media playback": "Du avbraut avspelinga.",
"A network error caused the media download to fail part-way.": "Ein nettverksfeil avbraut nedlasting av videoen.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Videoen kunne ikkje lastas ned, på grunn av ein nettverksfeil eller serverfeil, eller av di formatet ikkje er stoda.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Videoavspelinga blei broten på grunn av øydelagde data eller av di videoen ville gjera noe som nettlesaren din ikkje stodar.",
"No compatible source was found for this media.": "Fant ikke en kompatibel kilde for dette mediainnholdet."
});

View File

@ -1,26 +0,0 @@
{
"Play": "Spel",
"Pause": "Pause",
"Current Time": "Aktuell tid",
"Duration": "Varigheit",
"Remaining Time": "Tid attende",
"Stream Type": "Type straum",
"LIVE": "DIREKTE",
"Loaded": "Lasta inn",
"Progress": "Status",
"Fullscreen": "Fullskjerm",
"Non-Fullscreen": "Stenga fullskjerm",
"Mute": "Ljod av",
"Unmute": "Ljod på",
"Playback Rate": "Avspelingsrate",
"Subtitles": "Teksting på",
"subtitles off": "Teksting av",
"Captions": "Teksting for høyrselshemma på",
"captions off": "Teksting for høyrselshemma av",
"Chapters": "Kapitel",
"You aborted the media playback": "Du avbraut avspelinga.",
"A network error caused the media download to fail part-way.": "Ein nettverksfeil avbraut nedlasting av videoen.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "Videoen kunne ikkje lastas ned, på grunn av ein nettverksfeil eller serverfeil, eller av di formatet ikkje er stoda.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "Videoavspelinga blei broten på grunn av øydelagde data eller av di videoen ville gjera noe som nettlesaren din ikkje stodar.",
"No compatible source was found for this media.": "Fant ikke en kompatibel kilde for dette mediainnholdet."
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -664,7 +664,7 @@ body.vjs-full-window {
max-height: 25em;
}
.vjs-workinghover .vjs-menu-button-popup:hover .vjs-menu,
.vjs-workinghover .vjs-menu-button-popup.vjs-hover .vjs-menu,
.vjs-menu-button-popup .vjs-menu.vjs-lock-showing {
display: block;
}
@ -996,21 +996,23 @@ body.vjs-full-window {
.video-js .vjs-volume-panel {
transition: width 1s;
}
.video-js .vjs-volume-panel:hover .vjs-volume-control, .video-js .vjs-volume-panel:active .vjs-volume-control, .video-js .vjs-volume-panel:focus .vjs-volume-control, .video-js .vjs-volume-panel .vjs-volume-control:hover, .video-js .vjs-volume-panel .vjs-volume-control:active, .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active {
.video-js .vjs-volume-panel.vjs-hover .vjs-volume-control, .video-js .vjs-volume-panel:active .vjs-volume-control, .video-js .vjs-volume-panel:focus .vjs-volume-control, .video-js .vjs-volume-panel .vjs-volume-control:active, .video-js .vjs-volume-panel.vjs-hover .vjs-mute-control ~ .vjs-volume-control, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active {
visibility: visible;
opacity: 1;
position: relative;
transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, left 0s, top 0s;
}
.video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-horizontal, .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-horizontal, .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-horizontal {
.video-js .vjs-volume-panel.vjs-hover .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-horizontal, .video-js .vjs-volume-panel.vjs-hover .vjs-mute-control ~ .vjs-volume-control.vjs-volume-horizontal, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-horizontal {
width: 5em;
height: 3em;
margin-right: 0;
}
.video-js .vjs-volume-panel:hover .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control:hover.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-mute-control:hover ~ .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical {
.video-js .vjs-volume-panel.vjs-hover .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel:active .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel:focus .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control:active.vjs-volume-vertical, .video-js .vjs-volume-panel.vjs-hover .vjs-mute-control ~ .vjs-volume-control.vjs-volume-vertical, .video-js .vjs-volume-panel .vjs-volume-control.vjs-slider-active.vjs-volume-vertical {
left: -3.5em;
transition: left 0s;
}
.video-js .vjs-volume-panel.vjs-volume-panel-horizontal:hover, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active {
width: 9em;
.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-hover, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal:active, .video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-slider-active {
width: 10em;
transition: width 0.1s;
}
.video-js .vjs-volume-panel.vjs-volume-panel-horizontal.vjs-mute-toggle-only {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -0,0 +1,86 @@
videojs.addLanguage('ar', {
"Play": "تشغيل",
"Pause": "إيقاف",
"Current Time": "الوقت الحالي",
"Duration": "مدة",
"Remaining Time": "الوقت المتبقي",
"Stream Type": "نوع التيار",
"LIVE": "مباشر",
"Loaded": "تم التحميل",
"Progress": "التقدم",
"Fullscreen": "ملء الشاشة",
"Non-Fullscreen": "تعطيل ملء الشاشة",
"Mute": "صامت",
"Unmute": "غير الصامت",
"Playback Rate": "معدل التشغيل",
"Subtitles": "الترجمة",
"subtitles off": "إيقاف الترجمة",
"Captions": "التعليقات",
"captions off": "إيقاف التعليقات",
"Chapters": "فصول",
"You aborted the media playback": "لقد ألغيت تشغيل الفيديو",
"A network error caused the media download to fail part-way.": "تسبب خطأ في الشبكة بفشل تحميل الفيديو بالكامل.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "لا يمكن تحميل الفيديو بسبب فشل في الخادوم أو الشبكة ، أو فشل بسبب عدم إمكانية قراءة تنسيق الفيديو.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "تم إيقاف تشغيل الفيديو بسبب مشكلة فساد أو لأن الفيديو المستخدم يستخدم ميزات غير مدعومة من متصفحك.",
"No compatible source was found for this media.": "فشل العثور على أي مصدر متوافق مع هذا الفيديو.",
"Play Video": "تشغيل الفيديو",
"Close": "أغلق",
"Modal Window": "نافذة مشروطة",
"This is a modal window": "هذه نافذة مشروطة",
"This modal can be closed by pressing the Escape key or activating the close button.": "يمكن غلق هذه النافذة المشروطة عن طريق الضغط على زر الخروج أو تفعيل زر الإغلاق",
", opens captions settings dialog": ", تفتح نافذة خيارات التعليقات",
", opens subtitles settings dialog": ", تفتح نافذة خيارات الترجمة",
", selected": ", مختار",
"Audio Player": "مشغل الصوت",
"Video Player": "مشغل الفيديو",
"Replay": "إعادة التشغيل",
"Seek to live, currently behind live": "ذهاب إلى نقطة البث المباشر، متأخر عن البث المباشر حاليًا",
"Seek to live, currently playing live": "ذهاب إلى نقطة البث المباشر، البث المباشر قيد التشغيل حاليًا",
"Progress Bar": "شريط التقدم",
"Descriptions": "الأوصاف",
"descriptions off": "إخفاء الأوصاف",
"Audio Track": "المسار الصوتي",
"Volume Level": "مستوى الصوت",
"The media is encrypted and we do not have the keys to decrypt it.": "الوسائط مشفرة وليس لدينا الرموز اللازمة لفك شفرتها.",
"Close Modal Dialog": "إغلاق مربع الحوار المشروط",
", opens descriptions settings dialog": "، يفتح مربع حوار إعدادات الأوصاف",
"captions settings": "إعدادات التعليقات التوضيحية",
"subtitles settings": "إعدادات الترجمات",
"descriptions settings": "إعدادات الأوصاف",
"Text": "النص",
"White": "أبيض",
"Black": "أسود",
"Red": "أحمر",
"Green": "أخضر",
"Blue": "أزرق",
"Yellow": "أصفر",
"Magenta": "أرجواني",
"Cyan": "أزرق سماوي",
"Background": "الخلفية",
"Window": "نافذة",
"Transparent": "شفاف",
"Semi-Transparent": "نصف شفاف",
"Opaque": "معتم",
"Font Size": "حجم الخط",
"Text Edge Style": "نمط حواف النص",
"None": "لا شيء",
"Raised": "بارز",
"Depressed": "منخفض",
"Uniform": "منتظم",
"Dropshadow": "ظل خلفي",
"Font Family": "عائلة الخطوط",
"Proportional Sans-Serif": "Proportional Sans-Serif",
"Monospace Sans-Serif": "Monospace Sans-Serif",
"Proportional Serif": "Proportional Serif",
"Monospace Serif": "Monospace Serif",
"Casual": "Casual",
"Script": "Script",
"Small Caps": "Small Caps",
"Reset": "إعادة الضبط",
"restore all settings to the default values": "استعادة كل الإعدادات إلى القيم الافتراضية",
"Done": "تم",
"Caption Settings Dialog": "مربع حوار إعدادات التعليقات التوضيحية",
"Beginning of dialog window. Escape will cancel and close the window.": "بداية نافذة مربع حوار. الضغط على زر \"Escape\" سيؤدي إلى الإلغاء وإغلاق النافذة.",
"End of dialog window.": "نهاية نافذة مربع حوار.",
"{1} is loading.": "{1} قيد التحميل."
});

View File

@ -0,0 +1,86 @@
{
"Play": "تشغيل",
"Pause": "إيقاف",
"Current Time": "الوقت الحالي",
"Duration": "مدة",
"Remaining Time": "الوقت المتبقي",
"Stream Type": "نوع التيار",
"LIVE": "مباشر",
"Loaded": "تم التحميل",
"Progress": "التقدم",
"Fullscreen": "ملء الشاشة",
"Non-Fullscreen": "تعطيل ملء الشاشة",
"Mute": "صامت",
"Unmute": "غير الصامت",
"Playback Rate": "معدل التشغيل",
"Subtitles": "الترجمة",
"subtitles off": "إيقاف الترجمة",
"Captions": "التعليقات",
"captions off": "إيقاف التعليقات",
"Chapters": "فصول",
"You aborted the media playback": "لقد ألغيت تشغيل الفيديو",
"A network error caused the media download to fail part-way.": "تسبب خطأ في الشبكة بفشل تحميل الفيديو بالكامل.",
"The media could not be loaded, either because the server or network failed or because the format is not supported.": "لا يمكن تحميل الفيديو بسبب فشل في الخادوم أو الشبكة ، أو فشل بسبب عدم إمكانية قراءة تنسيق الفيديو.",
"The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "تم إيقاف تشغيل الفيديو بسبب مشكلة فساد أو لأن الفيديو المستخدم يستخدم ميزات غير مدعومة من متصفحك.",
"No compatible source was found for this media.": "فشل العثور على أي مصدر متوافق مع هذا الفيديو.",
"Play Video": "تشغيل الفيديو",
"Close": "أغلق",
"Modal Window": "نافذة مشروطة",
"This is a modal window": "هذه نافذة مشروطة",
"This modal can be closed by pressing the Escape key or activating the close button.": "يمكن غلق هذه النافذة المشروطة عن طريق الضغط على زر الخروج أو تفعيل زر الإغلاق",
", opens captions settings dialog": ", تفتح نافذة خيارات التعليقات",
", opens subtitles settings dialog": ", تفتح نافذة خيارات الترجمة",
", selected": ", مختار",
"Audio Player": "مشغل الصوت",
"Video Player": "مشغل الفيديو",
"Replay": "إعادة التشغيل",
"Seek to live, currently behind live": "ذهاب إلى نقطة البث المباشر، متأخر عن البث المباشر حاليًا",
"Seek to live, currently playing live": "ذهاب إلى نقطة البث المباشر، البث المباشر قيد التشغيل حاليًا",
"Progress Bar": "شريط التقدم",
"Descriptions": "الأوصاف",
"descriptions off": "إخفاء الأوصاف",
"Audio Track": "المسار الصوتي",
"Volume Level": "مستوى الصوت",
"The media is encrypted and we do not have the keys to decrypt it.": "الوسائط مشفرة وليس لدينا الرموز اللازمة لفك شفرتها.",
"Close Modal Dialog": "إغلاق مربع الحوار المشروط",
", opens descriptions settings dialog": "، يفتح مربع حوار إعدادات الأوصاف",
"captions settings": "إعدادات التعليقات التوضيحية",
"subtitles settings": "إعدادات الترجمات",
"descriptions settings": "إعدادات الأوصاف",
"Text": "النص",
"White": "أبيض",
"Black": "أسود",
"Red": "أحمر",
"Green": "أخضر",
"Blue": "أزرق",
"Yellow": "أصفر",
"Magenta": "أرجواني",
"Cyan": "أزرق سماوي",
"Background": "الخلفية",
"Window": "نافذة",
"Transparent": "شفاف",
"Semi-Transparent": "نصف شفاف",
"Opaque": "معتم",
"Font Size": "حجم الخط",
"Text Edge Style": "نمط حواف النص",
"None": "لا شيء",
"Raised": "بارز",
"Depressed": "منخفض",
"Uniform": "منتظم",
"Dropshadow": "ظل خلفي",
"Font Family": "عائلة الخطوط",
"Proportional Sans-Serif": "Proportional Sans-Serif",
"Monospace Sans-Serif": "Monospace Sans-Serif",
"Proportional Serif": "Proportional Serif",
"Monospace Serif": "Monospace Serif",
"Casual": "Casual",
"Script": "Script",
"Small Caps": "Small Caps",
"Reset": "إعادة الضبط",
"restore all settings to the default values": "استعادة كل الإعدادات إلى القيم الافتراضية",
"Done": "تم",
"Caption Settings Dialog": "مربع حوار إعدادات التعليقات التوضيحية",
"Beginning of dialog window. Escape will cancel and close the window.": "بداية نافذة مربع حوار. الضغط على زر \"Escape\" سيؤدي إلى الإلغاء وإغلاق النافذة.",
"End of dialog window.": "نهاية نافذة مربع حوار.",
"{1} is loading.": "{1} قيد التحميل."
}

View File

@ -62,13 +62,13 @@ videojs.addLanguage('de', {
"Depressed": "Gedrückt",
"Uniform": "Uniform",
"Dropshadow": "Schlagschatten",
"Font Family": "Schristfamilie",
"Font Family": "Schriftfamilie",
"Proportional Sans-Serif": "Proportionale Sans-Serif",
"Monospace Sans-Serif": "Monospace Sans-Serif",
"Proportional Serif": "Proportionale Serif",
"Monospace Serif": "Monospace Serif",
"Casual": "Zwanglos",
"Script": "Schreibeschrift",
"Script": "Schreibschrift",
"Small Caps": "Small-Caps",
"Reset": "Zurücksetzen",
"restore all settings to the default values": "Alle Einstellungen auf die Standardwerte zurücksetzen",
@ -80,7 +80,7 @@ videojs.addLanguage('de', {
"Video Player": "Video-Player",
"Progress Bar": "Forschrittsbalken",
"progress bar timing: currentTime={1} duration={2}": "{1} von {2}",
"Volume Level": "Lautstärkestufe",
"Volume Level": "Lautstärke",
"{1} is loading.": "{1} wird geladen.",
"Seek to live, currently behind live": "Zur Live-Übertragung wechseln. Aktuell wird es nicht live abgespielt.",
"Seek to live, currently playing live": "Zur Live-Übertragung wechseln. Es wird aktuell live abgespielt."

View File

@ -62,13 +62,13 @@
"Depressed": "Gedrückt",
"Uniform": "Uniform",
"Dropshadow": "Schlagschatten",
"Font Family": "Schristfamilie",
"Font Family": "Schriftfamilie",
"Proportional Sans-Serif": "Proportionale Sans-Serif",
"Monospace Sans-Serif": "Monospace Sans-Serif",
"Proportional Serif": "Proportionale Serif",
"Monospace Serif": "Monospace Serif",
"Casual": "Zwanglos",
"Script": "Schreibeschrift",
"Script": "Schreibschrift",
"Small Caps": "Small-Caps",
"Reset": "Zurücksetzen",
"restore all settings to the default values": "Alle Einstellungen auf die Standardwerte zurücksetzen",
@ -80,7 +80,7 @@
"Video Player": "Video-Player",
"Progress Bar": "Forschrittsbalken",
"progress bar timing: currentTime={1} duration={2}": "{1} von {2}",
"Volume Level": "Lautstärkestufe",
"Volume Level": "Lautstärke",
"{1} is loading.": "{1} wird geladen.",
"Seek to live, currently behind live": "Zur Live-Übertragung wechseln. Aktuell wird es nicht live abgespielt.",
"Seek to live, currently playing live": "Zur Live-Übertragung wechseln. Es wird aktuell live abgespielt."

Some files were not shown because too many files have changed in this diff Show More