aboutsummaryrefslogtreecommitdiff
path: root/roka.py
blob: c9643b206e359b0779a8710063add862ebe8ff64 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env python3

import argparse
import os
import shutil
import json
from flask import Flask, request, Response, render_template, send_file, templating
from flask.globals import app_ctx
from lib.books import Books
from lib.util import check_auth, escape, generate_rss, read_cache

abs_path = os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__)
config_path = os.path.join(abs_path, 'app.cfg')
config_exists = os.path.exists(config_path)
if config_exists or __name__.startswith('uwsgi'):
    app.config.from_pyfile(config_path)
cache_path = os.path.join(abs_path, 'cache')
json_path = os.path.join(cache_path, 'audiobooks.json')

@app.route('/')
def list_books():
    '''
    Book listing and audiobook RSS/file download

    :a: audiobook hash; if provided without :f: (track) return RSS
    :f: file hash; requires associated audiobook (:a:) to download

    Listing of audiobooks returned if no params provided
    '''
    books = read_cache(json_path)

    book = request.args.get('a')  # audiobook hash
    track = request.args.get('f') # file hash

    # audiobook and file parameters provided: serve up file
    if book and track:
        if not books.get(book) or not books[book]['files'].get(track):
            return 'book or file not found', 404

        track_path = books[book]['files'][track]['path']
        return send_file(track_path, conditional=True)

    # serve up audiobook RSS feed; only audiobook hash provided
    elif book:
        if not books.get(book):
            return 'book not found', 404

        rss = generate_rss(request.base_url, book, books)
        return Response(rss, mimetype='text/xml')

    else:
        auth = request.authorization
        if not auth or not check_auth(app, auth.username, auth.password):
            form = {'WWW-Authenticate': 'Basic realm="o/"'}
            return Response('unauthorized', 401, form)

        return render_template('index.html', books=books,
                               show_path=app.config.get('SHOW_PATH', True))

def generate(static_path, base_url, audiobook_dirs):
    static_index_path = os.path.join(static_path, 'index.html')

    books = Books()
    books.scan_books(audiobook_dirs)
    books.write_cache()
    books = read_cache(json_path)
    # A bit of a hack, but push to the app context stack so we can render a
    # template outside of a Flask request
    with app.app_context():
        app_ctx.push()
        index = render_template('index.html', books=books, static=True)
        app_ctx.pop()

    os.makedirs(static_path, exist_ok=True)

    indexfile = open(static_index_path, 'w')
    indexfile.write(index)
    indexfile.close()

    for b_key, book in books.items():
        rss = generate_rss(base_url, b_key, books, static=True)
        rss_path = os.path.join(static_path, b_key + '.xml')
        rssfile = open(rss_path, 'w')
        rssfile.write(rss.decode('utf-8'))
        rssfile.close()

        book_dir = os.path.join(static_path, b_key)
        os.makedirs(book_dir, exist_ok=True)

        for f_key, file in book['files'].items():
            f_path = file['path']
            copy_path = os.path.join(book_dir, f_key + '.mp3')
            if not os.path.exists(copy_path):
                shutil.copyfile(f_path, copy_path)

if __name__ == '__main__':
    desc = 'roka: listen to audiobooks with podcast apps via RSS'
    parser = argparse.ArgumentParser(description=desc)
    parser.add_argument('--scan', dest='scan', action='store_true',
                        help='scan audiobooks directory for new books',
                        required=False)
    parser.add_argument('--generate', dest='static_path', type=str, action='store',
                        help='Output directory to generate static files',
                        required=False)
    parser.add_argument('--config', dest='config', type=str, action='store',
                        help='Json configuration instead of app.cfg',
                        required=False)
    args = parser.parse_args()

    if args.config:
        class objectview(object):
            def __init__(self, d):
                self.__dict__ = d
        config = objectview(json.loads(args.config))
        # override app.cfg
        app.config.from_object(config)
    elif not config_exists:
        raise Exception(f"Config file '{config_path}' doesn't exist")

    root_path = os.path.expanduser(app.config['ROOT_PATH'])

    if args.scan:
        books = Books()
        books.scan_books(root_path)
        books.write_cache()
    elif args.static_path:
        generate(args.static_path, app.config['BASE_URL'], root_path)
    else:
        app.run(host='127.0.0.1', port='8085', threaded=True)