Repository: Orbiter/tedimg Branch: master Commit: f1d5d7aea812 Files: 21 Total size: 13.0 KB Directory structure: gitextract_rj62ikr9/ ├── .dockerignore ├── Dockerfile ├── LICENSE.md ├── README.md ├── docker/ │ ├── nginx.conf │ └── supervisor.conf ├── package.json ├── requirements.txt ├── resources/ │ ├── main.css │ └── main.js ├── run.py ├── tedimg/ │ ├── __init__.py │ ├── images.py │ ├── static/ │ │ └── empty │ ├── templates/ │ │ ├── banner.html │ │ ├── base.html │ │ ├── error.html │ │ ├── index.html │ │ └── show.html │ └── views.py └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ node_modules/ Dockerfile ================================================ FILE: Dockerfile ================================================ FROM python:3-alpine RUN apk add --no-cache nginx supervisor ADD . /app WORKDIR /app RUN apk add --no-cache nodejs \ && npm install \ && npm run build \ && rm -rf node_modules \ && apk del nodejs RUN apk add --no-cache --virtual build-dep gcc linux-headers libc-dev \ && apk add --no-cache jpeg-dev zlib-dev \ && pip install -r /app/requirements.txt \ && apk del build-dep EXPOSE 80 CMD /usr/bin/supervisord -c /app/docker/supervisor.conf ================================================ FILE: LICENSE.md ================================================ "THE BEER-WARE LICENSE" (Revision 42): kaiyou wrote this file. As long as you retain this notice you can do whatever you want with this stuff. If we meet some day, and you think this stuff is worth it, you can buy me a beer in return. ================================================ FILE: README.md ================================================ Image upload with Flask ======================= No database, no additional features. Plain and simple image upload. ![Screenshot](screenshot.png) Use the Docker image -------------------- Simply allocate a data directory and create the thumbnails sub-directory: mkdir -p /path/to/data/thumbnails Then run the image server: docker run --name=tedimg -d -v /path/to/data:/data kaiyou/tedimg Build from source ----------------- NodeJS and NPM are required to build from source : git clone git@github.com:kaiyou/tedimg.git cd tedimg npm install gulp docker build ================================================ FILE: docker/nginx.conf ================================================ user nginx; worker_processes 4; pid /run/nginx.pid; daemon off; events { worker_connections 768; } http { sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; server_tokens off; include /etc/nginx/mime.types; default_type application/octet-stream; access_log /dev/stdout; error_log /dev/stderr; gzip on; gzip_disable "msie6"; map $http_x_forwarded_proto $proxy_x_forwarded_proto { default $http_x_forwarded_proto; '' $scheme; } server { listen 80; client_max_body_size 20M; add_header X-Frame-Options 'SAMEORIGIN'; add_header X-Content-Type-Options 'nosniff'; add_header X-Permitted-Cross-Domain-Policies 'none'; add_header X-XSS-Protection '1; mode=block'; add_header Referrer-Policy 'same-origin'; location /data { root /; } location /static { root /app/tedimg; } location / { proxy_pass http://127.0.0.1:8000/; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto; } } } ================================================ FILE: docker/supervisor.conf ================================================ [supervisord] pidfile=/var/run/supervisord.pid nodaemon=true loglevel=DEBUG [program:nginx] command=nginx -c /app/docker/nginx.conf redirect_stderr=true [program:flask] directory=/app command = gunicorn -w 4 -b 127.0.0.1:8000 tedimg:app redirect_stderr=true ================================================ FILE: package.json ================================================ { "name": "tedimg", "scripts": { "watch": "webpack -w", "build": "webpack -p" }, "dependencies": { "css-loader": "^0.28.11", "file-loader": "^1.1.11", "jquery": "^3.3.1", "js-loader": "^0.1.1", "materialize-css": "^0.100.2", "resolve-url-loader": "^2.3.0", "style-loader": "^0.21.0", "url-loader": "^1.0.1", "webpack": "^4.9.1", "webpack-cli": "^2.1.4", "webpack-dev-server": "^3.1.4" } } ================================================ FILE: requirements.txt ================================================ Flask==1.0.2 Pillow==5.1.0 requests==2.18.4 gunicorn==19.8.1 Werkzeug==0.14.1 ================================================ FILE: resources/main.css ================================================ /* Browser specific (not valid) styles to make preformatted text wrap */ pre { white-space: pre-wrap; /* css-3 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */ white-space: -o-pre-wrap; /* Opera 7 */ word-wrap: break-word; /* Internet Explorer 5.5+ */ } input.snippet { font-family: monospace; } ::-webkit-input-placeholder { color: #444; } :-moz-placeholder { color: #444; } ::-moz-placeholder { color: #444; } :-ms-input-placeholder { color: #444; } /* Materialize icons */ /* fallback */ @font-face { font-family: 'Material Icons'; font-style: normal; font-weight: 400; src: url('https://fonts.gstatic.com/s/materialicons/v38/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2') format('woff2'); } .material-icons { font-family: 'Material Icons'; font-weight: normal; font-style: normal; font-size: 24px; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; word-wrap: normal; direction: ltr; -moz-font-feature-settings: 'liga'; -moz-osx-font-smoothing: grayscale; } ================================================ FILE: resources/main.js ================================================ // Import general CSS import 'materialize-css'; import 'materialize-css/dist/css/materialize.css'; // Import specific CSS import './main.css'; // Javascript libs import $ from 'jquery'; $(document).ready(function() { $("form#upload input").change(function() { $("form#upload").submit(); }); $("form#upload input[type=text]").on('paste', function() { setTimeout(function () { $("form#upload").submit(); }, 100); }); $("input.snippet").click(function() { this.select(); }) }); ================================================ FILE: run.py ================================================ from tedimg import app from flask import send_from_directory import os app.config.update( SITE_NAME="TeDomum Images", FULL_STORAGE="./tedimg/static/images", THUMB_STORAGE="./tedimg/static/images/thumb", FULL_WEB="static/images", THUMB_WEB="static/images/thumb" ) app.run(debug=True) ================================================ FILE: tedimg/__init__.py ================================================ from flask import Flask import os app = Flask(__name__) app.debug = "FLASK_DEBUG" in os.environ app.config.update( SITE_NAME=os.environ.get("SITE_NAME", "TedImg"), SOURCE_URL="https://git.tedomum.net/kaiyou/tedimg", HELP_URL="https://git.tedomum.net/kaiyou/tedimg", FULL_STORAGE=os.environ.get("FULL_STORAGE", "/data"), THUMB_STORAGE=os.environ.get("THUMB_STORAGE", "/data/thumb"), FULL_WEB=os.environ.get("FULL_WEB", "data"), THUMB_WEB=os.environ.get("THUMB_WEB", "data/thumb"), THUMB_SIZE=100 ) if (app.debug): from werkzeug import debug app.wsgi_app = debug.DebuggedApplication(app.wsgi_app, True) import tedimg.views ================================================ FILE: tedimg/images.py ================================================ from tedimg import app from PIL import Image, ImageSequence import os import binascii import requests import io import urllib def get_image(root, name): """ Try and get basic image attributes. """ filename = urllib.parse.quote(os.path.basename(name)) return (os.path.join(root, app.config["FULL_WEB"], filename), os.path.join(root, app.config["THUMB_WEB"], filename)) def image_from_file(file_storage): """ Try and read the uploaded file. """ image = Image.open(file_storage) return image def image_from_url(url): """ Try and download an image from the given url. """ response = requests.get(url) image = Image.open(io.BytesIO(response.content)) return image def save_with_thumbnail(image, filename): dest = "." while os.path.exists(os.path.join(app.config["FULL_STORAGE"], dest)): filename, _ = os.path.splitext(filename) ext = image.format.lower() random = binascii.hexlify(os.urandom(3)).decode('utf8') dest = "%s-%s.%s" % (filename, random, ext) # Grab some configuration full_file = os.path.join(app.config["FULL_STORAGE"], dest) thumb_file = os.path.join(app.config["THUMB_STORAGE"], dest) thumb_size = app.config["THUMB_SIZE"] # Save the image and thumbnail if image.format == 'GIF': image.save(full_file, format=image.format, save_all=True) else: image.save(full_file, format=image.format) image.thumbnail((thumb_size, thumb_size)) image.save(thumb_file, format=image.format) return dest ================================================ FILE: tedimg/static/empty ================================================ ================================================ FILE: tedimg/templates/banner.html ================================================ {% extends "base.html" %} {% block content %}
{% block banner_content %} {% endblock %}

{% block section_content %} {% endblock %}
{% endblock %} ================================================ FILE: tedimg/templates/base.html ================================================ {{ config["SITE_NAME"] }} {% block content %} {% endblock %} ================================================ FILE: tedimg/templates/error.html ================================================ {% extends "base.html" %} {% block content %}

Upload failed!

{{ message }}



{% endblock %} ================================================ FILE: tedimg/templates/index.html ================================================ {% extends "banner.html" %} {% block banner_content %}

Upload your image!

publish
public
{% endblock %} ================================================ FILE: tedimg/templates/show.html ================================================ {% extends "banner.html" %} {% block banner_content %}

Upload successful!

Direct link Thumbnail
{% endblock %} {% block section_content %}
Full size
image
code
chat
subject
Thumbnail
image
code
chat
subject
{% endblock %} ================================================ FILE: tedimg/views.py ================================================ from tedimg import app, images import flask import urllib import os @app.route('/') def index(): return flask.render_template("index.html") @app.route('/show/') def show(path): root = flask.url_for("index", _external=True) image, thumb = images.get_image(root, path) return flask.render_template("show.html", image=image, thumb=thumb) @app.route('/upload', methods=['POST']) def upload(): url = flask.request.form.get('url') uploaded = flask.request.files.get('file') # Get an image object from the uploaded image or URL try: if uploaded: image = images.image_from_file(uploaded) filename = os.path.basename(uploaded.filename) elif url: image = images.image_from_url(url) parsed = urllib.parse.urlparse(url) filename = os.path.basename(parsed.path) else: return flask.render_template("error.html", message="Missing image.") except Exception as error: __import__("traceback").print_exc() return flask.render_template("error.html", message="Could not store your image.") # Save the image to a local file result = images.save_with_thumbnail(image, filename) return flask.redirect(flask.url_for("show", path=result)) ================================================ FILE: webpack.config.js ================================================ var webpack = require("webpack"); var path = require("path"); module.exports = { mode: 'development', entry: './resources/main.js', output: { filename: 'app.js', path: path.resolve(__dirname, 'tedimg/static'), publicPath: '/static/' }, module: { rules: [ { test: /\.css$/, use: ['style-loader', 'css-loader', 'resolve-url-loader'] }, { test: /\.(png|woff2?)$/, use: ['file-loader'] } ] } }