from flask import (Flask, render_template, flash, redirect, url_for, request, send_from_directory, abort) from flask_bootstrap import Bootstrap from flask_wtf import FlaskForm from wtforms import DateTimeField, IntegerField, SubmitField from wtforms.validators import InputRequired, NumberRange from datetime import datetime, timedelta from tempfile import mkstemp import os import random import re import subprocess import time from config import Config app = Flask(__name__) app.config.from_object(Config) app.config['WTF_I18N_ENABLED'] = False # enables WTForms built-in translations bootstrap = Bootstrap(app) download_filenames = {} class DownloadForm(FlaskForm): class Meta: locales = ['de_DE', 'de'] start_time = DateTimeField('Startzeit', validators=[InputRequired()], format='%d.%m.%Y %H:%M') length = IntegerField('Länge', validators=[ InputRequired(), NumberRange( min=1, max=app.config['MAX_LENGTH'], message="Länge muss zwischen %(min)s and %(max)s Minuten sein")]) submit = SubmitField('Download') @app.route('/', methods=['GET', 'POST']) def download(): form = DownloadForm() if request.method == 'GET': form.start_time.data = datetime.now().replace(minute=0, second=0) - timedelta(hours=1) form.length.data = 60 elif form.validate_on_submit(): try: output_filename, download_filename = prepare_download(form) download_filenames[output_filename] = download_filename flash('Der Download sollte sofort starten.', 'success') return render_template('download.html', form=form, filename=output_filename) except ValueError as e: flash('Fehler beim Erstellen des Downloads: {}'.format(e), 'warning') else: flash('Fehler im Formular!', 'warning') return render_template('download.html', form=form) def clean_out_dir(): """Clean up OUT_DIR""" for fn in os.listdir(app.config['OUT_DIR']): if not fn.startswith('prolefeeder'): continue creation_time = os.path.getctime(os.path.join(app.config['OUT_DIR'], fn)) if time.time() - creation_time >= 60: os.unlink(os.path.join(app.config['OUT_DIR'], fn)) @app.route('/download_file/') def download_file(filename): """Download an output file""" if filename not in download_filenames: abort(404) download_filename = download_filenames.pop(filename) clean_out_dir() return send_from_directory(app.config['OUT_DIR'], filename, download_name=download_filename, as_attachment=True) def prepare_download(form): """Prepare a download given the user's request form""" def start_time_for_source_fn(fn): return datetime.strptime(fn, 'qfhi-%Y%m%d-%H%M.mp3') def length_for_source_fn(fn): size = os.stat(os.path.join(app.config['DATA_DIR'], fn)).st_size length = timedelta(minutes=size / (1000*app.config['KBITS']/8) / 60) return length # Get a sorted list of all source files with start time and length sources = [] for fn in os.listdir(app.config['DATA_DIR']): try: start_time = start_time_for_source_fn(fn) length = length_for_source_fn(fn) sources.append({'fn': fn, 'start_time': start_time, 'length': length}) except ValueError: pass sources = sorted(sources, key=lambda s: s['start_time']) # Only interested in the source files from the start file start_index = None for i, source in enumerate(sources): if source['start_time'] <= form.start_time.data < source['start_time'] + source['length']: start_index = i if start_index is None: raise ValueError('Konnte Startdatei nicht finden!') sources = sources[start_index:] # Super lazy: Limit to 5 source files input sources = sources[:5] # Seek into the first file ss = (form.start_time.data - sources[0]['start_time']).total_seconds() # Let ffmpeg do the rest of the job _, output_filename = mkstemp(prefix='prolefeeder', suffix='.mp3', dir=app.config['OUT_DIR']) output_filename = os.path.basename(output_filename) download_filename = '{}_{}.mp3'.format(form.start_time.data, form.length.data) c = [ 'ffmpeg', '-y', '-ss', str(ss), '-i', 'concat:' + '|'.join( [os.path.join(app.config['DATA_DIR'], s['fn']) for s in sources]), '-codec', 'copy', '-t', str(form.length.data * 60), os.path.join(app.config['OUT_DIR'], output_filename) ] app.logger.debug(' '.join(c)) subprocess.call(c) return output_filename, download_filename if __name__ == '__main__': app.run(debug=True)