Common ffmpeg_path().
[pyTivo/TheBayer.git] / config.py
blob35f4ee58d9cb0486cb48865fed1a536babc5e999
1 import ConfigParser
2 import getopt
3 import logging
4 import logging.config
5 import os
6 import re
7 import random
8 import string
9 import sys
10 from ConfigParser import NoOptionError
12 guid = ''.join([random.choice(string.letters) for i in range(10)])
14 config = ConfigParser.ConfigParser()
16 p = os.path.dirname(__file__)
17 config_files = ['/etc/pyTivo.conf', os.path.join(p, 'pyTivo.conf')]
18 configs_found = []
20 def init(argv):
21 global config_files
22 global configs_found
24 try:
25 opts, _ = getopt.getopt(argv, 'c:e:', ['config=', 'extraconf='])
26 except getopt.GetoptError, msg:
27 print msg
29 for opt, value in opts:
30 if opt in ('-c', '--config'):
31 config_files = [value]
32 elif opt in ('-e', '--extraconf'):
33 config_files.append(value)
35 configs_found = config.read(config_files)
36 if not configs_found:
37 print ('ERROR: pyTivo.conf does not exist.\n' +
38 'You must create this file before running pyTivo.')
39 sys.exit(1)
41 def reset():
42 global config
43 newconfig = ConfigParser.ConfigParser()
44 newconfig.read(config_files)
45 config = newconfig
47 def write():
48 f = open(configs_found[-1], 'w')
49 config.write(f)
50 f.close()
52 def getGUID():
53 if config.has_option('Server', 'GUID'):
54 return config.get('Server', 'GUID')
55 else:
56 return guid
58 def getTivoUsername(tsn=None):
59 return get_tsn('tivo_username', tsn)
61 def getTivoPassword(tsn=None):
62 return get_tsn('tivo_password', tsn)
64 def getBeaconAddresses():
65 if config.has_option('Server', 'beacon'):
66 beacon_ips = config.get('Server', 'beacon')
67 else:
68 beacon_ips = '255.255.255.255'
69 return beacon_ips
71 def getPort():
72 if config.has_option('Server', 'Port'):
73 return config.get('Server', 'Port')
74 else:
75 return '9032'
77 def get169Blacklist(tsn): # tivo does not pad 16:9 video
78 return tsn and not isHDtivo(tsn) and not get169Letterbox(tsn)
79 # verified Blacklist Tivo's are ('130', '240', '540')
80 # It is assumed all remaining non-HD and non-Letterbox tivos are Blacklist
82 def get169Letterbox(tsn): # tivo pads 16:9 video for 4:3 display
83 return tsn and tsn[:3] in ['649']
85 def get169Setting(tsn):
86 if not tsn:
87 return True
89 tsnsect = '_tivo_' + tsn
90 if config.has_section(tsnsect):
91 if config.has_option(tsnsect, 'aspect169'):
92 try:
93 return config.getboolean(tsnsect, 'aspect169')
94 except ValueError:
95 pass
97 if get169Blacklist(tsn) or get169Letterbox(tsn):
98 return False
100 return True
102 def getAllowedClients():
103 if config.has_option('Server', 'allowedips'):
104 return config.get('Server', 'allowedips').split()
105 else:
106 return []
108 def getExternalUrl():
109 if config.has_option('Server', 'externalurl'):
110 return config.get('Server', 'externalurl')
111 else:
112 return None
114 def getIsExternal(tsn):
115 tsnsect = '_tivo_' + tsn
116 if tsnsect in config.sections():
117 if config.has_option(tsnsect, 'external'):
118 try:
119 return config.getboolean(tsnsect, 'external')
120 except ValueError:
121 pass
123 return False
125 def isTsnInConfig(tsn):
126 return ('_tivo_' + tsn) in config.sections()
128 def getConfigTivoNames():
129 tivo_names = {}
130 for section in config.sections():
131 if section.startswith('_tivo_'):
132 tsn = section[6:]
133 if config.has_option(section, 'name'):
134 tivo_names[tsn] = config.get(section, 'name')
135 else:
136 tivo_names[tsn] = tsn
137 return tivo_names
139 def getShares(tsn=''):
140 shares = [(section, dict(config.items(section)))
141 for section in config.sections()
142 if not (section.startswith('_tivo_')
143 or section.startswith('logger_')
144 or section.startswith('handler_')
145 or section.startswith('formatter_')
146 or section in ('Server', 'loggers', 'handlers',
147 'formatters')
151 tsnsect = '_tivo_' + tsn
152 if config.has_section(tsnsect) and config.has_option(tsnsect, 'shares'):
153 # clean up leading and trailing spaces & make sure ref is valid
154 tsnshares = []
155 for x in config.get(tsnsect, 'shares').split(','):
156 y = x.strip()
157 if config.has_section(y):
158 tsnshares.append((y, dict(config.items(y))))
159 if tsnshares:
160 shares = tsnshares
162 return shares
164 def getDebug():
165 try:
166 return config.getboolean('Server', 'debug')
167 except NoOptionError, ValueError:
168 return False
170 def getOptres(tsn=None):
171 if tsn and config.has_section('_tivo_' + tsn):
172 try:
173 return config.getboolean('_tivo_' + tsn, 'optres')
174 except NoOptionError, ValueError:
175 pass
176 section_name = get_section(tsn)
177 if config.has_section(section_name):
178 try:
179 return config.getboolean(section_name, 'optres')
180 except NoOptionError, ValueError:
181 pass
182 try:
183 return config.getboolean('Server', 'optres')
184 except NoOptionError, ValueError:
185 return False
187 def getPixelAR(ref):
188 if config.has_option('Server', 'par'):
189 try:
190 return (True, config.getfloat('Server', 'par'))[ref]
191 except NoOptionError, ValueError:
192 pass
193 return (False, 1.0)[ref]
195 def ffmpeg_path():
196 return config.get('Server', 'ffmpeg')
198 def getFFmpegWait():
199 if config.has_option('Server', 'ffmpeg_wait'):
200 return max(int(float(config.get('Server', 'ffmpeg_wait'))), 1)
201 else:
202 return 10
204 def getFFmpegTemplate(tsn):
205 tmpl = get_tsn('ffmpeg_tmpl', tsn, True)
206 if tmpl:
207 return tmpl
208 return '%(video_codec)s %(video_fps)s %(video_br)s %(max_video_br)s \
209 %(buff_size)s %(aspect_ratio)s %(audio_br)s \
210 %(audio_fr)s %(audio_ch)s %(audio_codec)s %(audio_lang)s \
211 %(ffmpeg_pram)s %(format)s'
213 def getFFmpegPrams(tsn):
214 return get_tsn('ffmpeg_pram', tsn, True)
216 def isHDtivo(tsn): # tsn's of High Definition Tivo's
217 return bool(tsn and tsn[:3] in ['648', '652', '658', '663'])
219 def getValidWidths():
220 return [1920, 1440, 1280, 720, 704, 544, 480, 352]
222 def getValidHeights():
223 return [1080, 720, 480] # Technically 240 is also supported
225 # Return the number in list that is nearest to x
226 # if two values are equidistant, return the larger
227 def nearest(x, list):
228 return reduce(lambda a, b: closest(x, a, b), list)
230 def closest(x, a, b):
231 da = abs(x - a)
232 db = abs(x - b)
233 if da < db or (da == db and a > b):
234 return a
235 else:
236 return b
238 def nearestTivoHeight(height):
239 return nearest(height, getValidHeights())
241 def nearestTivoWidth(width):
242 return nearest(width, getValidWidths())
244 def getTivoHeight(tsn):
245 height = get_tsn('height', tsn)
246 if height:
247 return nearestTivoHeight(int(height))
248 return [480, 1080][isHDtivo(tsn)]
250 def getTivoWidth(tsn):
251 width = get_tsn('width', tsn)
252 if width:
253 return nearestTivoWidth(int(width))
254 return [544, 1920][isHDtivo(tsn)]
256 def _trunc64(i):
257 return max(int(strtod(i)) / 64000, 1) * 64
259 def getAudioBR(tsn=None):
260 rate = get_tsn('audio_br', tsn)
261 if not rate:
262 rate = '448k'
263 # convert to non-zero multiple of 64 to ensure ffmpeg compatibility
264 # compare audio_br to max_audio_br and return lowest
265 return str(min(_trunc64(rate), getMaxAudioBR(tsn))) + 'k'
267 def _k(i):
268 return str(int(strtod(i)) / 1000) + 'k'
270 def getVideoBR(tsn=None):
271 rate = get_tsn('video_br', tsn)
272 if rate:
273 return _k(rate)
274 return ['4096K', '16384K'][isHDtivo(tsn)]
276 def getMaxVideoBR(tsn=None):
277 rate = get_tsn('max_video_br', tsn)
278 if rate:
279 return _k(rate)
280 return '30000k'
282 def getVideoPCT(tsn=None):
283 pct = get_tsn('video_pct', tsn)
284 if pct:
285 return float(pct)
286 return 85
288 def getBuffSize(tsn=None):
289 size = get_tsn('bufsize', tsn)
290 if size:
291 return _k(size)
292 return ['1024k', '4096k'][isHDtivo(tsn)]
294 def getMaxAudioBR(tsn=None):
295 rate = get_tsn('max_audio_br', tsn)
296 # convert to non-zero multiple of 64 for ffmpeg compatibility
297 if rate:
298 return _trunc64(rate)
299 return 448
301 def get_section(tsn):
302 return ['_tivo_SD', '_tivo_HD'][isHDtivo(tsn)]
304 def get_tsn(name, tsn=None, raw=False):
305 if tsn and config.has_section('_tivo_' + tsn):
306 try:
307 return config.get('_tivo_' + tsn, name, raw)
308 except NoOptionError:
309 pass
310 section_name = get_section(tsn)
311 if config.has_section(section_name):
312 try:
313 return config.get(section_name, name, raw)
314 except NoOptionError:
315 pass
316 try:
317 return config.get('Server', name, raw)
318 except NoOptionError:
319 pass
320 return None
322 def getAudioCodec(tsn=None):
323 return get_tsn('audio_codec', tsn)
325 def getAudioCH(tsn=None):
326 return get_tsn('audio_ch', tsn)
328 def getAudioFR(tsn=None):
329 return get_tsn('audio_fr', tsn)
331 def getAudioLang(tsn=None):
332 return get_tsn('audio_lang', tsn)
334 def getCopyTS(tsn=None):
335 return get_tsn('copy_ts', tsn)
337 def getVideoFPS(tsn=None):
338 return get_tsn('video_fps', tsn)
340 # Parse a bitrate using the SI/IEEE suffix values as if by ffmpeg
341 # For example, 2K==2000, 2Ki==2048, 2MB==16000000, 2MiB==16777216
342 # Algorithm: http://svn.mplayerhq.hu/ffmpeg/trunk/libavcodec/eval.c
343 def strtod(value):
344 prefixes = {'y': -24, 'z': -21, 'a': -18, 'f': -15, 'p': -12,
345 'n': -9, 'u': -6, 'm': -3, 'c': -2, 'd': -1,
346 'h': 2, 'k': 3, 'K': 3, 'M': 6, 'G': 9,
347 'T': 12, 'P': 15, 'E': 18, 'Z': 21, 'Y': 24}
348 p = re.compile(r'^(\d+)(?:([yzafpnumcdhkKMGTPEZY])(i)?)?([Bb])?$')
349 m = p.match(value)
350 if not m:
351 raise SyntaxError('Invalid bit value syntax')
352 (coef, prefix, power, byte) = m.groups()
353 if prefix is None:
354 value = float(coef)
355 else:
356 exponent = float(prefixes[prefix])
357 if power == 'i':
358 # Use powers of 2
359 value = float(coef) * pow(2.0, exponent / 0.3)
360 else:
361 # Use powers of 10
362 value = float(coef) * pow(10.0, exponent)
363 if byte == 'B': # B == Byte, b == bit
364 value *= 8;
365 return value
367 def init_logging():
368 if (config.has_section('loggers') and
369 config.has_section('handlers') and
370 config.has_section('formatters')):
372 logging.config.fileConfig(config_files)
374 elif getDebug():
375 logging.basicConfig(level=logging.DEBUG)
376 else:
377 logging.basicConfig(level=logging.INFO)