mirror of
https://github.com/minetest/contentdb.git
synced 2024-12-22 14:02:24 +01:00
Add screenshot resolution checking
This commit is contained in:
parent
625e4cf9ee
commit
d08710684d
@ -27,6 +27,7 @@ from app.models import *
|
||||
from app.tasks.forumtasks import importTopicList, checkAllForumAccounts
|
||||
from app.tasks.importtasks import importRepoScreenshot, checkZipRelease, check_for_updates
|
||||
from app.utils import addNotification, get_system_user
|
||||
from app.utils.image import get_image_size
|
||||
|
||||
actions = {}
|
||||
|
||||
@ -54,8 +55,7 @@ def check_releases():
|
||||
|
||||
tasks = []
|
||||
for release in releases:
|
||||
zippath = release.url.replace("/uploads/", app.config["UPLOAD_DIR"])
|
||||
tasks.append(checkZipRelease.s(release.id, zippath))
|
||||
tasks.append(checkZipRelease.s(release.id, release.file_path))
|
||||
|
||||
result = group(tasks).apply_async()
|
||||
|
||||
@ -71,8 +71,7 @@ def reimport_packages():
|
||||
for package in Package.query.filter(Package.state!=PackageState.DELETED).all():
|
||||
release = package.releases.first()
|
||||
if release:
|
||||
zippath = release.url.replace("/uploads/", app.config["UPLOAD_DIR"])
|
||||
tasks.append(checkZipRelease.s(release.id, zippath))
|
||||
tasks.append(checkZipRelease.s(release.id, release.file_path))
|
||||
|
||||
result = group(tasks).apply_async()
|
||||
|
||||
@ -311,3 +310,16 @@ def remind_video_url():
|
||||
url_for('users.profile', username=user.username))
|
||||
|
||||
db.session.commit()
|
||||
|
||||
|
||||
@action("Update screenshot sizes")
|
||||
def remind_video_url():
|
||||
import sys
|
||||
|
||||
for screenshot in PackageScreenshot.query.all():
|
||||
width, height = get_image_size(screenshot.file_path)
|
||||
print(f"{screenshot.url}: {width}, {height}", file=sys.stderr)
|
||||
screenshot.width = width
|
||||
screenshot.height = height
|
||||
|
||||
db.session.commit()
|
||||
|
@ -17,7 +17,7 @@
|
||||
from celery import uuid
|
||||
from flask import *
|
||||
from flask_login import current_user, login_required
|
||||
from sqlalchemy import or_
|
||||
from sqlalchemy import or_, and_
|
||||
|
||||
from app.models import *
|
||||
from app.querybuilder import QueryBuilder
|
||||
@ -168,6 +168,11 @@ def view_user(username=None):
|
||||
Package.state == PackageState.CHANGES_NEEDED)) \
|
||||
.order_by(db.asc(Package.created_at)).all()
|
||||
|
||||
packages_with_small_screenshots = user.maintained_packages \
|
||||
.filter(Package.screenshots.any(and_(PackageScreenshot.width < PackageScreenshot.SOFT_MIN_SIZE[0],
|
||||
PackageScreenshot.height < PackageScreenshot.SOFT_MIN_SIZE[1]))) \
|
||||
.all()
|
||||
|
||||
outdated_packages = user.maintained_packages \
|
||||
.filter(Package.state != PackageState.DELETED,
|
||||
Package.update_config.has(PackageUpdateConfig.outdated_at.isnot(None))) \
|
||||
@ -185,7 +190,9 @@ def view_user(username=None):
|
||||
|
||||
return render_template("todo/user.html", current_tab="user", user=user,
|
||||
unapproved_packages=unapproved_packages, outdated_packages=outdated_packages,
|
||||
needs_tags=needs_tags, topics_to_add=topics_to_add)
|
||||
needs_tags=needs_tags, topics_to_add=topics_to_add,
|
||||
packages_with_small_screenshots=packages_with_small_screenshots,
|
||||
screenshot_min_size=PackageScreenshot.HARD_MIN_SIZE, screenshot_rec_size=PackageScreenshot.SOFT_MIN_SIZE)
|
||||
|
||||
|
||||
@bp.route("/users/<username>/update-configs/apply-all/", methods=["POST"])
|
||||
|
@ -67,7 +67,7 @@ is available.
|
||||
### Meta and packaging
|
||||
|
||||
* MUST: `screenshot.png` is present and up-to-date, with a correct aspect ratio (3:2, at least 300x200).
|
||||
* MUST: Have a high resolution cover image on ContentDB (at least 1280x768 pixels).
|
||||
* MUST: Have a high resolution cover image on ContentDB (at least 1280x720 pixels).
|
||||
It may be shown cropped to 16:9 aspect ratio, or shorter.
|
||||
* MUST: mod.conf/game.conf/texture_pack.conf present with:
|
||||
* name (if mod or game)
|
||||
|
@ -6,6 +6,7 @@ from app.logic.LogicError import LogicError
|
||||
from app.logic.uploads import upload_file
|
||||
from app.models import User, Package, PackageScreenshot, Permission, NotificationType, db, AuditSeverity
|
||||
from app.utils import addNotification, addAuditLog
|
||||
from app.utils.image import get_image_size
|
||||
|
||||
|
||||
def do_create_screenshot(user: User, package: Package, title: str, file, reason: str = None):
|
||||
@ -27,6 +28,13 @@ def do_create_screenshot(user: User, package: Package, title: str, file, reason:
|
||||
ss.url = uploaded_url
|
||||
ss.approved = package.checkPerm(user, Permission.APPROVE_SCREENSHOT)
|
||||
ss.order = counter
|
||||
ss.width, ss.height = get_image_size(uploaded_path)
|
||||
|
||||
if ss.is_too_small():
|
||||
raise LogicError(429,
|
||||
lazy_gettext("Screenshot is too small, it should be at least %(width)s by %(height)s pixels",
|
||||
width=PackageScreenshot.HARD_MIN_SIZE[0], height=PackageScreenshot.HARD_MIN_SIZE[1]))
|
||||
|
||||
db.session.add(ss)
|
||||
|
||||
if reason is None:
|
||||
|
@ -26,6 +26,7 @@ from sqlalchemy_utils.types import TSVectorType
|
||||
|
||||
from . import db
|
||||
from .users import Permission, UserRank, User
|
||||
from .. import app
|
||||
|
||||
|
||||
class PackageQuery(BaseQuery, SearchQueryMixin):
|
||||
@ -885,6 +886,10 @@ class PackageRelease(db.Model):
|
||||
# If the release is approved, then the task_id must be null and the url must be present
|
||||
CK_approval_valid = db.CheckConstraint("not approved OR (task_id IS NULL AND (url = '') IS NOT FALSE)")
|
||||
|
||||
@property
|
||||
def file_path(self):
|
||||
return self.url.replace("/uploads/", app.config["UPLOAD_DIR"])
|
||||
|
||||
def getAsDictionary(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
@ -986,6 +991,9 @@ class PackageRelease(db.Model):
|
||||
|
||||
|
||||
class PackageScreenshot(db.Model):
|
||||
HARD_MIN_SIZE = (920, 517)
|
||||
SOFT_MIN_SIZE = (1280, 720)
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
package_id = db.Column(db.Integer, db.ForeignKey("package.id"), nullable=False)
|
||||
@ -997,6 +1005,22 @@ class PackageScreenshot(db.Model):
|
||||
approved = db.Column(db.Boolean, nullable=False, default=False)
|
||||
created_at = db.Column(db.DateTime, nullable=False, default=datetime.datetime.utcnow)
|
||||
|
||||
width = db.Column(db.Integer, nullable=False)
|
||||
height = db.Column(db.Integer, nullable=False)
|
||||
|
||||
def is_very_small(self):
|
||||
return self.width < 720 or self.height < 405
|
||||
|
||||
def is_too_small(self):
|
||||
return self.width < PackageScreenshot.HARD_MIN_SIZE[0] or self.height < PackageScreenshot.HARD_MIN_SIZE[1]
|
||||
|
||||
def is_low_res(self):
|
||||
return self.width < PackageScreenshot.SOFT_MIN_SIZE[0] or self.height < PackageScreenshot.SOFT_MIN_SIZE[1]
|
||||
|
||||
@property
|
||||
def file_path(self):
|
||||
return self.url.replace("/uploads/", app.config["UPLOAD_DIR"])
|
||||
|
||||
def getEditURL(self):
|
||||
return url_for("packages.edit_screenshot",
|
||||
author=self.package.author.username,
|
||||
|
@ -27,6 +27,7 @@ from app.utils.git import clone_repo, get_latest_tag, get_latest_commit, get_tem
|
||||
from .minetestcheck import build_tree, MinetestCheckError, ContentType
|
||||
from ..logic.LogicError import LogicError
|
||||
from ..logic.packages import do_edit_package, ALIASES
|
||||
from ..utils.image import get_image_size
|
||||
|
||||
|
||||
@celery.task()
|
||||
@ -213,6 +214,10 @@ def importRepoScreenshot(id):
|
||||
ss.package = package
|
||||
ss.title = "screenshot.png"
|
||||
ss.url = "/uploads/" + filename
|
||||
ss.width, ss.height = get_image_size(destPath)
|
||||
if ss.is_too_small():
|
||||
return None
|
||||
|
||||
db.session.add(ss)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -6,6 +6,10 @@
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ _("Add a screenshot") }}</h1>
|
||||
<p class="mb-4">
|
||||
{{ _("The recommended resolution is 1920x1080, and screenshots must be at least %(width)dx%(height)d.",
|
||||
width=920, height=517) }}
|
||||
</p>
|
||||
|
||||
{% from "macros/forms.html" import render_field, render_submit_field %}
|
||||
<form method="POST" action="" enctype="multipart/form-data">
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
{% block content %}
|
||||
{% if package.checkPerm(current_user, "ADD_SCREENSHOTS") %}
|
||||
<a href="{{ package.getURL("packages.create_screenshot") }}" class="btn btn-primary float-right">
|
||||
<a href="{{ package.getURL('packages.create_screenshot') }}" class="btn btn-primary float-right">
|
||||
<i class="fas fa-plus mr-1"></i>
|
||||
{{ _("Add Image") }}
|
||||
</a>
|
||||
@ -26,16 +26,34 @@
|
||||
<i class="fas fa-bars"></i>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<img class="img-fluid" style="max-height: 64px;"
|
||||
src="{{ ss.getThumbnailURL() }}" alt="{{ ss.title }}" />
|
||||
<img class="img-fluid" style="max-height: 64px;" src="{{ ss.getThumbnailURL() }}" />
|
||||
</div>
|
||||
<div class="col">
|
||||
{{ ss.title }}
|
||||
{% if not ss.approved %}
|
||||
<div class="text-muted">
|
||||
{{ _("Awaiting approval") }}
|
||||
</div>
|
||||
|
||||
<div class="mt-1 text-muted">
|
||||
{{ ss.width }} x {{ ss.height }}
|
||||
{% if ss.is_low_res() %}
|
||||
{% if ss.is_very_small() %}
|
||||
<span class="badge badge-danger ml-3">
|
||||
{{ _("Way too small") }}
|
||||
</span>
|
||||
{% elif ss.is_too_small() %}
|
||||
<span class="badge badge-warning ml-3">
|
||||
{{ _("Too small") }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge badge-secondary ml-3">
|
||||
{{ _("Not HD") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if not ss.approved %}
|
||||
<span class="ml-3">
|
||||
{{ _("Awaiting approval") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<form action="{{ ss.getDeleteURL() }}" method="POST" class="col-auto text-right" role="form">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
|
||||
|
@ -14,6 +14,7 @@
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h2>{{ _("Unapproved Packages Needing Action") }}</h2>
|
||||
<div class="list-group mt-3 mb-5">
|
||||
{% for package in unapproved_packages %}
|
||||
@ -53,21 +54,75 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
<h2>{{ _("Potentially Outdated Packages") }}</h2>
|
||||
<p class="alert alert-info">
|
||||
{{ _("New: Git Update Detection has been set up on all packages to send notifications.") }}<br />
|
||||
{{ _("Consider changing the update settings to create releases automatically instead.") }}
|
||||
</p>
|
||||
<p>
|
||||
{{ _("Instead of marking packages as outdated, you can automatically create releases when New Commits or New Tags are pushed to Git by clicking 'Update Settings'.") }}
|
||||
{% if outdated_packages %}
|
||||
{{ _("To remove a package from below, create a release or change the update settings.") }}
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% from "macros/todo.html" import render_outdated_packages %}
|
||||
{{ render_outdated_packages(outdated_packages, current_user) }}
|
||||
|
||||
|
||||
<div class="mt-5"></div>
|
||||
<h2 id="small-screenshots">{{ _("Small Screenshots") }}</h2>
|
||||
{% if packages_with_small_screenshots %}
|
||||
<p>
|
||||
{{ _("These packages have screenshots that are too small, and should be replaced.") }}
|
||||
{{ _("Red and orange are screenshots below the limit, and grey screenshots are below the recommended resolution.") }}
|
||||
{{ _("The recommended resolution is 1920x1080, and screenshots must be at least %(width)dx%(height)d.",
|
||||
width=920, height=517) }}
|
||||
|
||||
<span class="badge badge-danger ml-3">
|
||||
{{ _("Way too small") }}
|
||||
</span>
|
||||
<span class="badge badge-warning">
|
||||
{{ _("Too small") }}
|
||||
</span>
|
||||
<span class="badge badge-secondary">
|
||||
{{ _("Not HD") }}
|
||||
</span>
|
||||
</p>
|
||||
{% endif %}
|
||||
<div class="list-group mt-3 mb-5">
|
||||
{% for package in packages_with_small_screenshots %}
|
||||
<a class="list-group-item list-group-item-action" href="{{ package.getURL('packages.screenshots') }}">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 text-muted" style="min-width: 200px;">
|
||||
<img
|
||||
class="img-fluid"
|
||||
style="max-height: 22px; max-width: 22px;"
|
||||
src="{{ package.getThumbnailOrPlaceholder() }}" />
|
||||
|
||||
<span class="pl-2">
|
||||
{{ package.title }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-sm">
|
||||
{% for ss in package.screenshots %}
|
||||
{% if ss.is_low_res() %}
|
||||
{% if ss.is_very_small() %}
|
||||
{% set badge_color = "badge-danger" %}
|
||||
{% elif ss.is_too_small() %}
|
||||
{% set badge_color = "badge-warning" %}
|
||||
{% else %}
|
||||
{% set badge_color = "badge-secondary" %}
|
||||
{% endif %}
|
||||
<span class="badge {{ badge_color }} ml-2" title="{{ ss.title }}">
|
||||
{{ ss.width }} x {{ ss.height }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% else %}
|
||||
<p class="text-muted">{{ _("Nothing to do :)") }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
<a class="btn btn-secondary float-right" href="{{ url_for('todo.tags', author=user.username) }}">
|
||||
{{_ ("See All") }}</a>
|
||||
<h2>{{ _("Packages Without Tags") }}</h2>
|
||||
|
24
app/utils/image.py
Normal file
24
app/utils/image.py
Normal file
@ -0,0 +1,24 @@
|
||||
# ContentDB
|
||||
# Copyright (C) 2022 rubenwardy
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from typing import Tuple
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def get_image_size(path: str) -> Tuple[int,int]:
|
||||
im = Image.open(path)
|
||||
return im.size
|
26
migrations/versions/f6ef5f35abca_.py
Normal file
26
migrations/versions/f6ef5f35abca_.py
Normal file
@ -0,0 +1,26 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: f6ef5f35abca
|
||||
Revises: 011e42c52d21
|
||||
Create Date: 2022-01-26 00:10:46.610784
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'f6ef5f35abca'
|
||||
down_revision = '011e42c52d21'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('package_screenshot', sa.Column('height', sa.Integer(), nullable=False, server_default="0"))
|
||||
op.add_column('package_screenshot', sa.Column('width', sa.Integer(), nullable=False, server_default="0"))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('package_screenshot', 'width')
|
||||
op.drop_column('package_screenshot', 'height')
|
Loading…
Reference in New Issue
Block a user