server.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import logging
  2. import yaml
  3. from flask import Flask, request, url_for
  4. import ycast.vtuner as vtuner
  5. import ycast.radiobrowser as radiobrowser
  6. PATH_ROOT = 'ycast'
  7. PATH_CUSTOM_STATIONS = 'my_stations'
  8. PATH_RADIOBROWSER = 'radiobrowser'
  9. PATH_RADIOBROWSER_COUNTRY = 'country'
  10. PATH_RADIOBROWSER_GENRE = 'genre'
  11. PATH_RADIOBROWSER_POPULAR = 'popular'
  12. PATH_RADIOBROWSER_SEARCH = 'search'
  13. my_stations = {}
  14. app = Flask(__name__)
  15. def run(config, address='0.0.0.0', port=8010):
  16. try:
  17. get_stations(config)
  18. app.run(host=address, port=port)
  19. except PermissionError:
  20. logging.error("No permission to create socket. Are you trying to use ports below 1024 without elevated rights?")
  21. def get_stations(config):
  22. global my_stations
  23. if not config:
  24. logging.warning("If you want to use the 'My Stations' feature, please supply a valid station configuration")
  25. return
  26. try:
  27. with open(config, 'r') as f:
  28. my_stations = yaml.safe_load(f)
  29. except FileNotFoundError:
  30. logging.error("Station configuration '%s' not found", config)
  31. return
  32. except yaml.YAMLError as e:
  33. logging.error("Config error: %s", e)
  34. return
  35. # TODO: vtuner doesn't do https (e.g. for logos). make an icon cache
  36. @app.route('/', defaults={'path': ''})
  37. @app.route('/setupapp/<path:path>')
  38. @app.route('/' + PATH_ROOT + '/', defaults={'path': ''})
  39. def landing(path):
  40. if request.args.get('token') == '0':
  41. return vtuner.get_init_token()
  42. page = vtuner.Page()
  43. page.add(vtuner.Directory('Radiobrowser', url_for('radiobrowser_landing', _external=True)))
  44. page.add(vtuner.Directory('My Stations', url_for('custom_stations_landing', _external=True)))
  45. return page.to_string()
  46. @app.route('/' + PATH_ROOT + '/' + PATH_CUSTOM_STATIONS + '/')
  47. def custom_stations_landing():
  48. page = vtuner.Page()
  49. page.add(vtuner.Previous(url_for("landing", _external=True)))
  50. if not my_stations:
  51. page.add(vtuner.Display("No stations found"))
  52. else:
  53. for category in sorted(my_stations, key=str.lower):
  54. directory = vtuner.Directory(category, url_for('custom_stations_category', _external=True, category=category))
  55. page.add(directory)
  56. return page.to_string()
  57. @app.route('/' + PATH_ROOT + '/' + PATH_CUSTOM_STATIONS + '/<category>')
  58. def custom_stations_category(category):
  59. page = vtuner.Page()
  60. page.add(vtuner.Previous(url_for('custom_stations_landing', _external=True)))
  61. if category not in my_stations:
  62. page.add(vtuner.Display("Category '" + category + "' not found"))
  63. else:
  64. for station in sorted(my_stations[category], key=str.lower):
  65. station = vtuner.Station(None, station, None, my_stations[category][station], None, None, None, None, None, None)
  66. page.add(station)
  67. return page.to_string()
  68. @app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/')
  69. def radiobrowser_landing():
  70. page = vtuner.Page()
  71. page.add(vtuner.Previous(url_for('landing', _external=True)))
  72. page.add(vtuner.Directory('Genres', url_for('radiobrowser_genres', _external=True)))
  73. page.add(vtuner.Directory('Countries', url_for('radiobrowser_countries', _external=True)))
  74. page.add(vtuner.Directory('Most Popular', url_for('radiobrowser_popular', _external=True)))
  75. page.add(vtuner.Search('Search', url_for('radiobrowser_search', _external=True, path='')))
  76. return page.to_string()
  77. @app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_COUNTRY + '/')
  78. def radiobrowser_countries():
  79. countries = radiobrowser.get_countries()
  80. page = vtuner.Page()
  81. page.add(vtuner.Previous(url_for('radiobrowser_landing', _external=True)))
  82. for country in countries:
  83. page.add(vtuner.Directory(country, url_for('radiobrowser_country_stations', _external=True, country=country)))
  84. return page.to_string()
  85. @app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_COUNTRY + '/<country>')
  86. def radiobrowser_country_stations(country):
  87. stations = radiobrowser.get_stations_by_country(country)
  88. page = vtuner.Page()
  89. page.add(vtuner.Previous(url_for('radiobrowser_countries', _external=True)))
  90. if len(stations) == 0:
  91. page.add(vtuner.Display("No stations found for country '" + country + "'"))
  92. else:
  93. for station in stations:
  94. page.add(vtuner.Station(station.id, station.name, ', '.join(station.tags), station.url, station.icon, station.tags[0], station.country, station.codec, station.bitrate, None))
  95. return page.to_string()
  96. @app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_GENRE + '/')
  97. def radiobrowser_genres():
  98. genres = radiobrowser.get_genres()
  99. page = vtuner.Page()
  100. page.add(vtuner.Previous(url_for('radiobrowser_landing', _external=True)))
  101. for genre in genres:
  102. page.add(vtuner.Directory(genre, url_for('radiobrowser_genre_stations', _external=True, genre=genre)))
  103. return page.to_string()
  104. @app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_GENRE + '/<genre>')
  105. def radiobrowser_genre_stations(genre):
  106. stations = radiobrowser.get_stations_by_genre(genre)
  107. page = vtuner.Page()
  108. page.add(vtuner.Previous(url_for('radiobrowser_genres', _external=True)))
  109. if len(stations) == 0:
  110. page.add(vtuner.Display("No stations found for genre '" + genre + "'"))
  111. else:
  112. for station in stations:
  113. page.add(vtuner.Station(station.id, station.name, ', '.join(station.tags), station.url, station.icon, station.tags[0], station.country, station.codec, station.bitrate, None))
  114. return page.to_string()
  115. @app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_POPULAR + '/')
  116. def radiobrowser_popular():
  117. stations = radiobrowser.get_stations_by_votes()
  118. page = vtuner.Page()
  119. page.add(vtuner.Previous(url_for('radiobrowser_landing', _external=True)))
  120. for station in stations:
  121. page.add(vtuner.Station(station.id, station.name, ', '.join(station.tags), station.url, station.icon, station.tags[0], station.country, station.codec, station.bitrate, None))
  122. return page.to_string()
  123. @app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_SEARCH, defaults={'path': ''})
  124. @app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_SEARCH + '<path:path>')
  125. def radiobrowser_search(path):
  126. page = vtuner.Page()
  127. page.add(vtuner.Previous(url_for('radiobrowser_landing', _external=True)))
  128. # vtuner does totally weird stuff here: TWO request arguments are passed to the search URI
  129. # thus, we need to parse the search query as path
  130. query = None
  131. if 'search' in path:
  132. path_search = path[path.find('search'):]
  133. query = path_search.partition('=')[2]
  134. if not query or len(query) < 3:
  135. page.add(vtuner.Display("Search query too short."))
  136. else:
  137. stations = radiobrowser.search(query)
  138. if len(stations) == 0:
  139. page.add(vtuner.Display("No results for '" + query + "'"))
  140. else:
  141. for station in stations:
  142. page.add(vtuner.Station(station.id, station.name, ', '.join(station.tags), station.url, station.icon, station.tags[0], station.country, station.codec, station.bitrate, None))
  143. return page.to_string()