taiko-web development has returned to GitHub. This Gitea instance will be shut down soon.
If your taiko-web fork is unavailable due to DMCA takedown, you can contact GitHub Support and ask for it to be deleted.
Taiko no Tatsujin simulator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

app.py 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. #!/usr/bin/env python2
  2. from __future__ import division
  3. import json
  4. import sqlite3
  5. import tempfile
  6. import re
  7. import os
  8. from flask import Flask, g, jsonify, render_template, request, abort, redirect
  9. from flask_caching import Cache
  10. from ffmpy import FFmpeg
  11. app = Flask(__name__)
  12. try:
  13. app.cache = Cache(app, config={'CACHE_TYPE': 'redis'})
  14. except RuntimeError:
  15. app.cache = Cache(app, config={'CACHE_TYPE': 'filesystem', 'CACHE_DIR': tempfile.gettempdir()})
  16. DATABASE = 'taiko.db'
  17. DEFAULT_URL = 'https://github.com/bui/taiko-web/'
  18. def get_db():
  19. db = getattr(g, '_database', None)
  20. if db is None:
  21. db = g._database = sqlite3.connect(DATABASE)
  22. return db
  23. def query_db(query, args=(), one=False):
  24. cur = get_db().execute(query, args)
  25. rv = cur.fetchall()
  26. cur.close()
  27. return (rv[0] if rv else None) if one else rv
  28. def get_config():
  29. if os.path.isfile('config.json'):
  30. try:
  31. config = json.load(open('config.json', 'r'))
  32. except ValueError:
  33. print 'WARNING: Invalid config.json, using default values'
  34. config = {}
  35. else:
  36. print 'WARNING: No config.json found, using default values'
  37. config = {}
  38. if not config.get('songs_baseurl'):
  39. config['songs_baseurl'] = ''.join([request.host_url, 'songs']) + '/'
  40. if not config.get('assets_baseurl'):
  41. config['assets_baseurl'] = ''.join([request.host_url, 'assets']) + '/'
  42. config['_version'] = get_version()
  43. return config
  44. def parse_osu(osu):
  45. osu_lines = open(osu, 'r').read().replace('\x00', '').split('\n')
  46. sections = {}
  47. current_section = (None, [])
  48. for line in osu_lines:
  49. line = line.strip()
  50. secm = re.match('^\[(\w+)\]$', line)
  51. if secm:
  52. if current_section:
  53. sections[current_section[0]] = current_section[1]
  54. current_section = (secm.group(1), [])
  55. else:
  56. if current_section:
  57. current_section[1].append(line)
  58. else:
  59. current_section = ('Default', [line])
  60. if current_section:
  61. sections[current_section[0]] = current_section[1]
  62. return sections
  63. def get_osu_key(osu, section, key, default=None):
  64. sec = osu[section]
  65. for line in sec:
  66. ok = line.split(':', 1)[0].strip()
  67. ov = line.split(':', 1)[1].strip()
  68. if ok.lower() == key.lower():
  69. return ov
  70. return default
  71. def get_preview(song_id, song_type):
  72. preview = 0
  73. if song_type == "tja":
  74. if os.path.isfile('public/songs/%s/main.tja' % song_id):
  75. preview = get_tja_preview('public/songs/%s/main.tja' % song_id)
  76. else:
  77. osus = [osu for osu in os.listdir('public/songs/%s' % song_id) if osu in ['easy.osu', 'normal.osu', 'hard.osu', 'oni.osu']]
  78. if osus:
  79. osud = parse_osu('public/songs/%s/%s' % (song_id, osus[0]))
  80. preview = int(get_osu_key(osud, 'General', 'PreviewTime', 0))
  81. return preview
  82. def get_tja_preview(tja):
  83. tja_lines = open(tja, 'r').read().replace('\x00', '').split('\n')
  84. for line in tja_lines:
  85. line = line.strip()
  86. if ':' in line:
  87. name, value = line.split(':', 1)
  88. if name.lower() == 'demostart':
  89. value = value.strip()
  90. try:
  91. value = float(value)
  92. except ValueError:
  93. pass
  94. else:
  95. return int(value * 1000)
  96. elif line.lower() == '#start':
  97. break
  98. return 0
  99. def get_version():
  100. version = {'commit': None, 'commit_short': '', 'version': None, 'url': DEFAULT_URL}
  101. if os.path.isfile('version.json'):
  102. try:
  103. ver = json.load(open('version.json', 'r'))
  104. except ValueError:
  105. print('Invalid version.json file')
  106. return version
  107. for key in version.keys():
  108. if ver.get(key):
  109. version[key] = ver.get(key)
  110. return version
  111. @app.teardown_appcontext
  112. def close_connection(exception):
  113. db = getattr(g, '_database', None)
  114. if db is not None:
  115. db.close()
  116. @app.route('/')
  117. def route_index():
  118. version = get_version()
  119. return render_template('index.html', version=version, config=get_config())
  120. @app.route('/api/preview')
  121. @app.cache.cached(timeout=15)
  122. def route_api_preview():
  123. song_id = request.args.get('id', None)
  124. if not song_id or not re.match('^[0-9]+$', song_id):
  125. abort(400)
  126. song_row = query_db('select * from songs where id = ? and enabled = 1', (song_id,))
  127. if not song_row:
  128. abort(400)
  129. song_type = song_row[0][12]
  130. prev_path = make_preview(song_id, song_type)
  131. if not prev_path:
  132. return redirect(get_config()['songs_baseurl'] + '%s/main.mp3' % song_id)
  133. return redirect(get_config()['songs_baseurl'] + '%s/preview.mp3' % song_id)
  134. @app.route('/api/songs')
  135. @app.cache.cached(timeout=15)
  136. def route_api_songs():
  137. songs = query_db('select * from songs where enabled = 1')
  138. raw_categories = query_db('select * from categories')
  139. categories = {}
  140. def_category = {'title': None, 'title_en': None}
  141. for cat in raw_categories:
  142. categories[cat[0]] = cat[1]
  143. raw_song_skins = query_db('select * from song_skins')
  144. song_skins = {}
  145. for skin in raw_song_skins:
  146. song_skins[skin[0]] = {'name': skin[1], 'song': skin[2], 'stage': skin[3], 'don': skin[4]}
  147. songs_out = []
  148. for song in songs:
  149. song_id = song[0]
  150. song_type = song[12]
  151. preview = get_preview(song_id, song_type)
  152. category_out = categories[song[11]] if song[11] in categories else def_category
  153. song_skin_out = song_skins[song[14]] if song[14] in song_skins else None
  154. songs_out.append({
  155. 'id': song_id,
  156. 'title': song[1],
  157. 'title_lang': song[2],
  158. 'subtitle': song[3],
  159. 'subtitle_lang': song[4],
  160. 'stars': [
  161. song[5], song[6], song[7], song[8], song[9]
  162. ],
  163. 'preview': preview,
  164. 'category': category_out,
  165. 'type': song_type,
  166. 'offset': song[13],
  167. 'song_skin': song_skin_out
  168. })
  169. return jsonify(songs_out)
  170. @app.route('/api/config')
  171. @app.cache.cached(timeout=15)
  172. def route_api_config():
  173. config = get_config()
  174. return jsonify(config)
  175. def make_preview(song_id, song_type):
  176. song_path = 'public/songs/%s/main.mp3' % song_id
  177. prev_path = 'public/songs/%s/preview.mp3' % song_id
  178. if os.path.isfile(song_path) and not os.path.isfile(prev_path):
  179. preview = get_preview(song_id, song_type) / 1000
  180. if not preview or preview <= 0.1:
  181. print('Skipping #%s due to no preview' % song_id)
  182. return False
  183. print('Making preview.mp3 for song #%s' % song_id)
  184. ff = FFmpeg(inputs={song_path: '-ss %s' % preview},
  185. outputs={prev_path: '-codec:a libmp3lame -ar 32000 -b:a 92k -y -loglevel panic'})
  186. ff.run()
  187. return prev_path
  188. if __name__ == '__main__':
  189. app.run(port=34801)