Commit 4e4e5efc authored by root's avatar root

initial commit

parents
# virtualenv
flask
# database
app.db
db_repository
# py stuff
__pycache__
*.pyc
# pictures
*.png
*.jpeg
*.jpg
# static files (thumbnails etc)
app2/static/thumbnails
thumbnails
# tmp files
*.swp
# config file
config.py
Videobums
==========
How do I run this?
----------
create a virtual environment named flask, using `virtualenv /usr/bin/python2.7 flask`
next install flask and necessary extensions with:
`$ flask/bin/pip install` + the needed package
after a secret key was set in [config.py](config.py) the testserver can be run with `./run.py`
Note: To fill the Database with videos edit [scraper.py](scraper.py) to contain your desired url and run it with
`./scraper.py` + path
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask_flatpages import FlatPages
from config import basedir
app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)
pages = FlatPages(app)
from app2 import views, models
from flask.ext.wtf import Form
from wtforms import StringField, BooleanField
from wtforms.validators import DataRequired
class SearchForm(Form):
search = StringField('search', validators=[DataRequired()])
sglobal = BooleanField('global', default=False)
from app2 import app, db
import os
from config import PATH_TO_THUMBNAILS
import datetime
class Video(db.Model):
id = db.Column(db.String, primary_key=True)
title = db.Column(db.String)
path = db.Column(db.String, unique=True)
url = db.Column(db.String)
category = db.Column(db.String)
duration = db.Column(db.Float)
vwidth = db.Column(db.Integer)
vheight = db.Column(db.Integer)
vcodec = db.Column(db.String)
acodec = db.Column(db.String)
timestamp = db.Column(db.DateTime)
blacklisted = db.Column(db.Boolean)
thumbnail = db.Column(db.Boolean)
def __repr__(self):
return '<Video %r>' % (self.title)
def thumbnail(self):
return os.path.join(PATH_TO_THUMBNAILS, str(self.id)+".jpg")
def get_duration(self):
return str(datetime.timedelta(seconds=int(self.duration)))
title: FAQ
date: 2015-02-26
Advanced Mammut Search FAQ
==========================
Q: How? <br />
A: Proudly made with html5 and css3 and without javascript and flash!
Q: Where are the files from? <br />
A: The files are uploaded by users of [Mammut](https://daten.stusta.mhn.de/) file service.
Q: Why is my Video not indexed?<br />
A: At the moment only mp4 files with h246 video and aac audio are supported.
Q: How are the categories made?<br />
A: Videos are sorted dynamically by file-/foldername.
Q: Why do only some videos play directly in my browser?<br />
A: Only some codecs are supportet by html5.
Q: How about my privacy?<br />
A: No Information is logged, tracked or stored.
Q: What is further planned?<br />
A: Tags generated by highly sophisticated neuronal networks (humans), supporting more video formats, upvote downvote system
Q: But your site is buggy!<br />
A: Please send an email to AllgemeineSupportundOrganisationsMailVerteilerListeallerAdministratorendesStuStaNet_e.V.derStudentenStadtMuenchenFreimann@stusta.mhn.de .
body{
background-color:#101010;
color: #FFFFFF;
padding: 0px;
margin: 1px;
font-family: 'Helvetica Neue', Georgia;
}
header{
text-align: left;
padding: 0px 115px;
}
header .beta {
position: absolute;
pointer-events: none;
transform: translateY(15px) rotate(-30deg);
-webkit-transform: translateY(15px) rotate(-30deg);
left: 225px;
}
header a.logo {
margin: 0;
}
header p, header a {
margin: 11px 10px;
}
header a {
float: left;
}
a{
color: white;
text-decoration: none;
}
.text a{
color: #BBF;
text-decoration: underline;
}
.footer a{
color: #BBF;
text-decoration: underline;
}
.detailcont{
background-color: #020202;
padding: 10px;
}
.text{
background-color: #151515;
padding: 10px;
margin: 10px 115px;
}
.flash{
text-align: center;
background-color: #151515;
font-size: 16px;
border-width: thin;
border-color: #202020;
border-style:dotted;
margin: 7px auto;
}
.topbar{
background-color: #151515;
border-style:solid none;
border-width: thin;
border-color:#202020;
text-align: left;
padding: 0 115px;
}
.topbar .search {
float: right;
display: inline-block;
margin: 2px 0px;
}
.clear {
clear: both;
}
.topbar .categories {
display: inline-block;
float: left;
height: 32px;
}
.topbar .categories a {
display: inline-block;
vertical-align: middle;
padding: 9px 24px;
}
.topbar .categories a.active, .topbar .categories a:hover {
background: #252525;
}
.topbar .categories a:hover {
transition: 0.3s;
}
.container2{
background-color: #020202;
}
.container{
background-color:#020202;
display:-webkit-inline-flex;
display: flex;
-webkit-flex-direction: row;
-webkit-flex-wrap: wrap;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-content: center;
align-items: stretch;
}
.container a {
width: 320px;
margin: 5px;
overflow: hidden;
}
.ibox{
background-color: #000000;
/* background-color: #151515; */
border-style: solid;
border-width: thin;
border-color: #202020;
}
.ibox span {
display: block;
margin: 5px 0 0 5px;
}
.ibox .duration {
background: rgba(0,0,0,0.6);
padding: 2px 5px;
position: absolute;
bottom: 0;
right: 0;
}
.ibox .title {
}
.vdetail{
text-align: center;
}
.video{
width: 640px;
margin: 0px auto;
}
.bottom{
align-self: baseline;
text-align: center;
}
.dl{
width: 100%;
}
#button{
height: 30px;
width: 30px;
vertical-align:middle;
border-style: none;
background-image: url("search-icon.png");
border: 1px solid #202020;
box-sizing: content-box;
padding: 0px;
}
#search{
color: #FFFFFF;
height: 32px;
box-sizing: border-box;
border-style: solid;
border-width: thin;
border-color: #202020;
border-right: none;
background-color: #000000;
vertical-align: middle;
padding: 5px 60px 5px 10px;
text-align: left;
}
.thumbnail{
height: 173px;
margin-bottom: 5px;
position: relative;
background-repeat: no-repeat;
}
@keyframes preview{
0% {background-position: 0px 0px;}
100% {background-position: -2240px 0px;}
}
@-webkit-keyframes preview{
0% {background-position: 0px 0px;}
100% {background-position: -2240px 0px;}
}
.gay .search::before {
content: "";
display: block;
background: -webkit-linear-gradient(left, #e40303 16%,#ff8c00 16%,#ff8c00 16%,#ff8c00 32%,#ffed00 32%,#ffed00 48%,#008026 48%,#008026 64%,#004dff 64%,#004dff 80%,#750787 80%,#750787 100%);
background: -moz-linear-gradient(left, #e40303 16%,#ff8c00 16%,#ff8c00 16%,#ff8c00 32%,#ffed00 32%,#ffed00 48%,#008026 48%,#008026 64%,#004dff 64%,#004dff 80%,#750787 80%,#750787 100%);
background: linear-gradient(to right, #e40303 16%,#ff8c00 16%,#ff8c00 16%,#ff8c00 32%,#ffed00 32%,#ffed00 48%,#008026 48%,#008026 64%,#004dff 64%,#004dff 80%,#750787 80%,#750787 100%);
height: 3px;
position: absolute;
left: 0;
margin-top: -6px;
width: 100%;
}
.piss .search::before {
content: "";
display: block;
background-color: #dfbb16;
height: 3px;
position: absolute;
left: 0;
margin-top: -6px;
width: 100%;
}
.scat .search::before {
content: "";
display: block;
background-color: #502412;
height: 3px;
position: absolute;
left: 0;
margin-top: -6px;
width: 100%;
}
.links{
width: 100%;
margin-right: 10px;
background-color: #151515;
border-style: solid none;
border-width: thin;
border-color: #202020;
display: inline-flex;
justify-content: space-between;
}
#categories{
height: 20px;
text-align: right;
margin-right: 20px;
}
.count {
height: 20px;
text-align: left;
float: right;
}
.title {
height: 20px;
text-align: left;
float: left;
}
a.ibox:hover, .pagination a:hover, .dl a:hover {
border-color: #555;
transition: 0.3s;
}
a.ibox:hover .thumbnail{
animation-duration: 4s;
animation-name: preview;
animation-timing-function: steps(7, end);
animation-iteration-count: infinite;
-webkit-animation-iteration-count: infinite;
-webkit-animation-duration: 3s;
-webkit-animation-name: preview;
-webkit-animation-timing-function: steps(7, end);
}
.footer{
text-align: center;
font-size: 10px;
border-style: solid none;
border-width: thin;
border-color: #202020;
}
.pagination, .dl {
display: inline-block;
margin: 15px;
}
.pagination .active {
border-color: #555;
}
.pagination strong, .pagination a, .dl a {
border: 1px solid #222;
display: inline-block;
padding: 7px;
height: 35px;
box-sizing: border-box;
vertical-align: middle;
margin: 0;
}
<!DOCTYPE html>
<html lang ="en">
<head>
<title>{% block title %} StuTube - Advanced Mammut Search Engine {% endblock %}</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<link rel="icon" href="{{url_for('static', filename='favicon.png') }}" type="image/png" />
</head>
<body {% if query == 'gay' %} class="gay" {% endif %}{% if query == 'scat' %} class="scat" {% endif %}{% if query == 'piss' %} class="piss" {% endif %}>
<header>
<a class="logo" href="{{url_for('index')}}"><img src="{{ url_for('static', filename='logo.png') }}" width="150"></a>
<img src="{{ url_for('static', filename='beta.png') }}" class="beta" />
<p class='title' >Advanced Mammut Search Engine</p> <a href="{{url_for('page',category=category, path='faq')}}">(FAQ)</a>
<p class='count'>
Urlaubsvideos in Database: {{vidcount}}
</p>
<div class="clear"></div>
</header>
<div class='topbar'>
<div class='categories'>
{% for cat in categories %}
<a {% if cat.category == category %}class="active"{% endif %} href="{{url_for('index', category=cat.category)}}">
{{cat.category}}
</a>
{% endfor %}
</div>
<form class="search" action="{{ url_for('search', category=category) }}" method="post" name="search">{{ g.search_form.hidden_tag() }}{{ g.search_form.search(size=21, placeholder="Search in " + category,value=query) }}{{g.search_form.sglobal }}
<input id='button' type="submit" value=''>
</form>
<div class="clear"></div>
</div>
<div class="container2">
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class='flash'>
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
</div>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<div class="footer">
<p>Disclaimer: All content on this site is uploaded by users and is not moderated. For inqueries see:
<a href="{{url_for('page',category=category, path='faq')}}">FAQ</a> or
<a href="https://stusta.de/site/Meta:Impressum">Impressum</a></p>
</div>
</body>
</html>
{% extends "base.html" %}
{% block content %}
<div class="container">
{% for video in videos.items%}
<a class="ibox" href="{{url_for('video',category=category, id=video.id) }}" title="{{video.title}}">
<div class="thumbnail" style="background-image: url({{url_for('static', filename=video.thumbnail())}});">
<span class="duration">{{video.get_duration()}}</span>
</div>
<span class="title">{{video.title}}</span>
</a>
{% endfor %}
</div>
<div class='bottom'>
{{render_pagination(videos,'index')}}
</div>
</div>
{% endblock%}
{% macro render_pagination(pagination, endpoint) %}
<div class=pagination>
{%- for page in pagination.iter_pages() %}
{% if page %}
{% if page != pagination.page %}
<a href="{{ url_for(endpoint,category=category, page=page) }}">{{ page }}</a>
{% else %}
<strong class="active">{{ page }}</strong>
{% endif %}
{% else %}
<span class=ellipsis></span>
{% endif %}
{%- endfor %}
{% endmacro %}
{% extends "base.html" %}
{% block content %}
<div class="detailcont">
<div class="text">
{{ page.html|safe }}
</div>
</div>
{% endblock content %}
{% extends "base.html" %}
{% block content %}
<div class="container">
{% for video in videos.items%}
<a class="ibox" href="{{url_for('video',category=category, id=video.id) }}" title="{{video.title}}">
<div class="thumbnail" style="background-image: url({{url_for('static', filename=video.thumbnail())}});">
<span class="duration">{{video.get_duration()}}<span>
</div>
<span class="title">{{video.title}}</span>
</a>
{% endfor %}
</div>
<div class='bottom'>
{{render_pagination(videos,'search_results')}}
</div>
</div>
{% endblock%}
{% macro render_pagination(pagination, endpoint) %}
<div class=pagination>
{%- for page in pagination.iter_pages() %}
{% if page %}
{% if page != pagination.page %}
<a href="{{ url_for(endpoint,category=category, query=query, page=page) }}">{{ page }}</a>
{% else %}
<strong class="active">{{ page }}</strong>
{% endif %}
{% else %}
<span class=ellipsis></span>
{% endif %}
{%- endfor %}
{% endmacro %}
{% extends "base.html" %}
{% block title %}{{ video.title }} - StuTube{% endblock %}
{% block content %}
<div class='vdetail'>
<h1>{{ video.title }}</h1>
<div class='video'>
<video autoplay id="{{video.id}}" controls preload="auto" width="720" height="480" data-setup="{}">
<source src="{{ video.url }}" type='video/mp4'>
</video>
</div>
<div class=dl>
<a href="{{video.url}}" download="{{video.title+".mp4"}}">Download</a>
</div>
</div>
{% endblock %}
from flask import render_template, flash, redirect, g, url_for
from app2 import app, db, models, pages
from .forms import SearchForm
from .models import Video
from config import VIDEOS_PER_PAGE
def generic_video_query():
return Video.query\
.filter((((Video.vcodec == "h264") & (Video.acodec == "aac"))
| ((Video.vcodec == "h264") & (Video.acodec == None))
| ((Video.vcodec == "aac") & (Video.acodec == "h264"))
| ((Video.vcodec == None) & (Video.acodec == "h264")))
& (Video.duration > 120) & (Video.blacklisted == False))\
.order_by(Video.timestamp.desc())
@app.before_request
def before_request():
g.search_form = SearchForm()
@app.route('/')
@app.route('/index')
@app.route('/index/<category>')
@app.route('/index/<category>/<int:page>')
def index(category='Stusta', page=1):
videos = generic_video_query()
categories = db.session.query(Video.category).distinct()\
.order_by(Video.category).all()
vidcount = videos.count()
videos = videos.filter(Video.category == category)\
.paginate(page, VIDEOS_PER_PAGE, False)
return render_template('index.html', vidcount=vidcount, category=category,
videos=videos, categories=categories)
@app.route('/search')
@app.route('/search/<category>', methods=['GET', 'POST'])
def search(category):
if g.search_form.validate_on_submit():
if g.search_form.sglobal.data==True:
return redirect(url_for('search_results_all',
query=g.search_form.search.data))
else:
return redirect(url_for('search_results', category=category,
query=g.search_form.search.data))
return redirect(url_for('index', category=category))
@app.route('/search/global/results/<query>')
@app.route('/search/global/results/<query>/<int:page>')
def search_results_all(query, page=1):
words = query.split()
videos = generic_video_query()
categories = db.session.query(Video.category).distinct().all()
vidcount = videos.count()
for word in words:
videos = videos.filter(Video.title.ilike("%"+word+"%"))
recount = videos.count()
videos = videos.paginate(page, VIDEOS_PER_PAGE, False)
flash("%d Results for \'%s\'" % (recount, query))
return render_template('search.html', vidcount=vidcount, category="global",