]> git.immae.eu Git - perso/Immae/Projets/Python/MusicSampler.git/blob - music_sampler/helpers.py
Add config key to config.yml to store command line arguments
[perso/Immae/Projets/Python/MusicSampler.git] / music_sampler / helpers.py
1 # -*- coding: utf-8 -*-
2 import argparse
3 import sys
4 import os
5 import math
6 import sounddevice as sd
7 import logging
8 import gettext
9 import yaml
10 gettext.install('music_sampler')
11 Logger = logging.getLogger("kivy")
12
13 from . import sysfont
14
15 class Config:
16 pass
17
18 def find_font(name, style=sysfont.STYLE_NONE):
19 if getattr(sys, 'frozen', False):
20 font = sys._MEIPASS + "/fonts/{}_{}.ttf".format(name, style)
21 else:
22 font = sysfont.get_font(name, style=style)
23 if font is not None:
24 font = font[4]
25 return font
26
27 def register_fonts():
28 from kivy.core.text import LabelBase
29
30 ubuntu_regular = find_font("Ubuntu", style=sysfont.STYLE_NORMAL)
31 ubuntu_bold = find_font("Ubuntu", style=sysfont.STYLE_BOLD)
32 symbola = find_font("Symbola")
33
34 if ubuntu_regular is None:
35 error_print("Font Ubuntu regular could not be found, "
36 "please install it.", exit=True)
37 if symbola is None:
38 error_print("Font Symbola could not be found, please install it.",
39 exit=True)
40 if ubuntu_bold is None:
41 warn_print("Font Ubuntu Bold could not be found.")
42
43 LabelBase.register(name="Ubuntu",
44 fn_regular=ubuntu_regular,
45 fn_bold=ubuntu_bold)
46 LabelBase.register(name="Symbola",
47 fn_regular=symbola)
48
49
50 def path():
51 if getattr(sys, 'frozen', False):
52 return sys._MEIPASS + "/"
53 else:
54 return os.path.dirname(os.path.realpath(__file__))
55
56
57 Configs = {
58 'music_path': {
59 'abbr': '-p',
60 'default': '.',
61 'help': _("Folder in which to find the music files"),
62 'type': None
63 },
64 'latency': {
65 'abbr': '-l',
66 'default': 'high',
67 'help': _("Latency: low, high or number of seconds"),
68 'type': None
69 },
70 'language': {
71 'abbr': '-L',
72 'default': "fr",
73 'help': _("Select another language"),
74 'type': None
75 },
76 'device': {
77 'abbr': '-d',
78 'default': None,
79 'help': _("Select this sound device"),
80 'type': None
81 },
82 'blocksize': {
83 'abbr': '-b',
84 'default': 0,
85 'help': _("Blocksize: If not 0, the number of frames to take\
86 at each step for the mixer"),
87 'type': int
88 },
89 'frame_rate': {
90 'abbr': '-f',
91 'default': 44100,
92 'help': _("Frame rate to play the musics"),
93 'type': int
94 },
95 'channels': {
96 'abbr': '-x',
97 'default': 2,
98 'help': _("Number of channels to use"),
99 'type': int
100 },
101 'sample_width': {
102 'abbr': '-s',
103 'default': 2,
104 'help': _("Sample width (number of bytes for each frame)"),
105 'type': int
106 },
107 'builtin_mixing': {
108 'default': False,
109 'help_yes': _("Make the mixing of sounds manually\
110 (do it if the system cannot handle it correctly)"),
111 'help_no': _("Don't make the mixing of sounds manually (default)"),
112 'type': 'boolean'
113 },
114 'debug': {
115 'abbr': '-d',
116 'default': False,
117 'help_yes': _("Print messages in console"),
118 'help_no': _("Don't print messages in console (default)"),
119 'type': 'boolean'
120 },
121 'focus_warning': {
122 'default': True,
123 'help_yes': _("Show a warning when focus is lost (default)"),
124 'help_no': _("Don't show warning when focus is lost"),
125 'type': 'boolean'
126 },
127 'list_devices': {
128 'help': _("List available sound devices"),
129 'type': 'action'
130 },
131 }
132 Configs_order = [
133 'debug',
134 'music_path',
135 'builtin_mixing',
136 'latency',
137 'blocksize',
138 'frame_rate',
139 'channels',
140 'sample_width',
141 'focus_warning',
142 'language',
143 'list_devices',
144 'device',
145 ]
146 def parse_args():
147 argv = sys.argv[1 :]
148 sys.argv = sys.argv[: 1]
149 if "--" in argv:
150 index = argv.index("--")
151 kivy_args = argv[index+1 :]
152 argv = argv[: index]
153
154 sys.argv.extend(kivy_args)
155
156 os.environ["KIVY_NO_CONFIG"] = 'true'
157 sys.argv.extend(["-c", "kivy:log_level:warning"])
158 sys.argv.extend(["-c", "kivy:log_dir:/tmp"])
159 sys.argv.extend(["-c", "kivy:log_name:/tmp/music_sampler_%_.txt"])
160
161 parser = argparse.ArgumentParser(
162 argument_default=argparse.SUPPRESS,
163 description=_("A Music Sampler application."))
164 parser.add_argument("-V", "--version",
165 action="version",
166 help=_("Displays the current version and exits. Only use\
167 in bundled package"),
168 version=show_version())
169 parser.add_argument("-c", "--config",
170 default="config.yml",
171 required=False,
172 help=_("Config file to load (default: config.yml)"))
173 for argument in Configs_order:
174 arg = Configs[argument]
175 if arg['type'] != 'boolean' and arg['type'] != 'action':
176 parser.add_argument(arg['abbr'], '--' + argument.replace('_', '-'),
177 type=arg['type'],
178 help=arg['help']+_(" (default: {})").format(arg['default']))
179 elif arg['type'] == 'boolean':
180 parser.add_argument('--' + argument.replace('_', '-'),
181 action='store_const', const=True,
182 help=arg['help_yes'])
183 parser.add_argument('--no-' + argument.replace('_', '-'),
184 action='store_const', const=True,
185 help=arg['help_no'])
186 else:
187 parser.add_argument('--' + argument.replace('_', '-'),
188 action='store_const', const=True,
189 help=arg['help'])
190 parser.add_argument('--',
191 dest="args",
192 help=_("Kivy arguments. All arguments after this are interpreted\
193 by Kivy. Pass \"-- --help\" to get Kivy's usage."))
194
195 args = parser.parse_args(argv)
196
197 Config.yml_file = args.config
198 build_config(args)
199
200 if Config.device is not None:
201 sd.default.device = Config.device
202
203 if Config.list_devices:
204 print(sd.query_devices())
205 sys.exit()
206
207 if Config.debug:
208 sys.argv.extend(["-c", "kivy:log_level:debug"])
209
210 if Config.language != 'en':
211 gettext.translation("music_sampler",
212 localedir=path() + '/locales',
213 languages=[Config.language]).install()
214 if not Config.music_path.endswith("/"):
215 Config.music_path = Config.music_path + "/"
216
217 def build_config(args):
218 stream = open(Config.yml_file, "r")
219 try:
220 config = yaml.safe_load(stream)
221 except Exception as e:
222 error_print("Error while loading config file: {}".format(e))
223 config = {}
224 stream.close()
225 if 'config' in config:
226 config = config['config']
227 else:
228 config = {}
229
230 for config_item in Configs_order:
231 if Configs[config_item]['type'] != 'boolean' and \
232 Configs[config_item]['type'] != 'action':
233 t = Configs[config_item]['type'] or str
234 if hasattr(args, config_item):
235 setattr(Config, config_item, getattr(args, config_item))
236 elif config_item in config:
237 setattr(Config, config_item, t(config[config_item]))
238 else:
239 setattr(Config, config_item, Configs[config_item]['default'])
240 elif Configs[config_item]['type'] == 'boolean':
241 if hasattr(args, 'no_' + config_item) or hasattr(args, config_item):
242 setattr(Config, config_item, hasattr(args, config_item))
243 elif config_item in config:
244 setattr(Config, config_item, config[config_item])
245 else:
246 setattr(Config, config_item, Configs[config_item]['default'])
247 else:
248 setattr(Config, config_item, hasattr(args, config_item))
249
250
251 def show_version():
252 if getattr(sys, 'frozen', False):
253 with open(path() + ".pyinstaller_commit", "r") as f:
254 return f.read()
255 else:
256 return _("option '-V' can only be used in bundled package")
257
258 def duration_to_min_sec(duration):
259 minutes = int(duration / 60)
260 seconds = int(duration) % 60
261 if minutes < 100:
262 return "{:2}:{:0>2}".format(minutes, seconds)
263 else:
264 return "{}:{:0>2}".format(minutes, seconds)
265
266 def gain(volume, old_volume=None):
267 if old_volume is None:
268 return 20 * math.log10(max(volume, 0.1) / 100)
269 else:
270 return [
271 20 * math.log10(max(volume, 0.1) / max(old_volume, 0.1)),
272 max(volume, 0)]
273
274 def debug_print(message, with_trace=None):
275 if with_trace is None:
276 with_trace = (Logger.getEffectiveLevel() < logging.WARN)
277 with_trace &= (sys.exc_info()[0] is not None)
278
279 Logger.debug('MusicSampler: ' + message, exc_info=with_trace)
280
281 def error_print(message, exit=False, with_trace=None):
282 if with_trace is None:
283 with_trace = (Logger.getEffectiveLevel() < logging.WARN)
284 with_trace &= (sys.exc_info()[0] is not None)
285
286 # FIXME: handle it correctly when in a thread
287 if exit:
288 Logger.critical('MusicSampler: ' + message, exc_info=with_trace)
289 sys.exit(1)
290 else:
291 Logger.error('MusicSampler: ' + message, exc_info=with_trace)
292
293 def warn_print(message, with_trace=None):
294 if with_trace is None:
295 with_trace = (Logger.getEffectiveLevel() < logging.WARN)
296 with_trace &= (sys.exc_info()[0] is not None)
297
298 Logger.warn('MusicSampler: ' + message, exc_info=with_trace)
299