PageRenderTime 43ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/irank/core.py

https://github.com/gfxmonk/python-irank
Python | 187 lines | 179 code | 8 blank | 0 comment | 8 complexity | e94f644ba5cea90984dffdbc14a00d59 MD5 | raw file
  1. import re, sys
  2. import db
  3. irank_marker = re.compile("\\[([^]=]+)=([0-5])\\]")
  4. fsenc = sys.getfilesystemencoding()
  5. def version():
  6. with open(os.path.join(os.path.dirname(__file__), '../VERSION')) as f:
  7. return f.read().strip()
  8. import os, sys
  9. KEYS = ['rating', 'Mood', 'Softness', 'Nostalgia']
  10. try:
  11. with open(os.path.expanduser("~/.config/irank/ratings")) as f:
  12. strip = lambda x: x.strip()
  13. identity = lambda x: x
  14. KEYS = filter(identity, map(strip, f.readlines()))
  15. except StandardError:
  16. print >> sys.stderr, ("Using the default rating keys.\n" +
  17. "You can make your own by writing them one line at a time to ~/.config/irank/ratings")
  18. class BaseSong(object):
  19. def __init__(self, filename):
  20. self.filename = filename
  21. try:
  22. self._open_file(self.filename)
  23. except ValueError, e:
  24. raise ValueError("file %s: %s" % (filename, e))
  25. assert self.file, "Failed to load file: %s" % (filename,)
  26. self.artist = self._get_artist()
  27. self.title = self._get_title()
  28. comment = self._get_comment()
  29. self.values = Values(comment)
  30. def save(self):
  31. self._set_comment(self.values.flatten())
  32. assert self._get_comment() == self.values.flatten(), "Could not save tag!"
  33. self.file.save()
  34. def check(self):
  35. orig_comment = self._get_comment()
  36. new_comment = 'irank-test-comment'
  37. self._set_comment(new_comment)
  38. try:
  39. actual_comment = self._get_comment()
  40. return actual_comment == new_comment
  41. finally:
  42. self._set_comment(orig_comment)
  43. class MutagenSong(BaseSong):
  44. DEFAULT_COMMENT = u''
  45. DEFAULT_LANG='eng'
  46. DEFAULT_LANG='eng'
  47. COMMENT_KEY = u"COMM::'%s'" % (DEFAULT_LANG,)
  48. FALLBACK_KEYS = [
  49. u"COMM::'XXX'",
  50. u"COMM:c0:'XXX'",
  51. ]
  52. def _make_comment(self, text):
  53. import mutagen
  54. return mutagen.id3.COMM(encoding=3, lang=self.DEFAULT_LANG, desc=u'', text=unicode(text))
  55. def _tag_value(self, tag):
  56. if tag is None: return u''
  57. return tag.text[0]
  58. def _open_file(self, path):
  59. import mutagen
  60. self.file = mutagen.File(path)
  61. if self.file and not hasattr(self.file, 'tags'):
  62. self.file.add_tags()
  63. def _get_comment(self):
  64. # debugging...
  65. self._possible_comments = list(filter(lambda item: item[0].startswith('COMM:'), self.file.items()))
  66. comment = None
  67. for key in [self.COMMENT_KEY] + self.FALLBACK_KEYS:
  68. comment = self.file.get(key, None)
  69. if comment is not None:
  70. break
  71. if comment is None:
  72. comment = self._make_comment(self.DEFAULT_COMMENT)
  73. return self._tag_value(comment)
  74. def _set_comment(self, comment):
  75. #self._comment.text = [unicode(comment)]
  76. self.file[self.COMMENT_KEY] = self._make_comment(comment)
  77. def _get_artist(self):
  78. tag = self.file.get('TPE1')
  79. return self._tag_value(tag)
  80. def _get_title(self):
  81. tag = self.file.get('TIT2')
  82. return self._tag_value(tag)
  83. class TaglibSong(BaseSong):
  84. def _open_file(self, path):
  85. from tagpy import FileRef
  86. self.file = FileRef(path)
  87. self.tags = self.file.tag()
  88. def _get_comment(self):
  89. return self.tags.comment
  90. def _set_comment(self, comment):
  91. self.tags.comment = comment
  92. def _get_artist(self):
  93. return self.tags.artist
  94. def _get_title(self):
  95. return self.tags.title
  96. #Set the default Song implementation:
  97. Song = MutagenSong
  98. #Song = TaglibSong
  99. class Values(dict):
  100. def __init__(self, str=''):
  101. if str:
  102. self.__parse(str)
  103. def __parse(self, comment):
  104. for match in irank_marker.finditer(comment):
  105. key, value = match.groups()
  106. self[key] = int(value)
  107. def __str__(self):
  108. summary = []
  109. for k in KEYS:
  110. v = self[k]
  111. line = "%s %s" % self.__format(k,v)
  112. summary.append(line)
  113. return "\n".join(summary)
  114. def __format(self, k, v, key_width=None):
  115. if key_width is None:
  116. key_width = max(map(len, KEYS)) + 1
  117. key_str = "%%%ss" % (key_width,) % (k,)
  118. value_str = "%-5s" % ("*" * v, )
  119. return key_str, value_str
  120. def formatted_pairs(self, key_width=None):
  121. return [self.__format(k,v, key_width) for k,v in self.items()]
  122. def format_line(self, key_width = 1):
  123. return " ".join(":".join(pair) for pair in self.formatted_pairs(key_width))
  124. def __get_real_key(self, key):
  125. if key in KEYS:
  126. return key
  127. for real_key, sanitised_key in zip(KEYS, map(db.sanitise_column_name, KEYS)):
  128. if key == sanitised_key:
  129. return real_key
  130. raise KeyError(key)
  131. def __setitem__(self, k, v):
  132. k = self.__get_real_key(k)
  133. super(type(self), self).__setitem__(k,v)
  134. def __getitem__(self, k):
  135. k = self.__get_real_key(k)
  136. return self.get(k,0)
  137. def items(self):
  138. for k in KEYS:
  139. yield (k, self[k])
  140. def keys(self):
  141. return KEYS
  142. def __len__(self):
  143. return len(KEYS)
  144. def values(self):
  145. return map(self.__getitem__, KEYS)
  146. def flatten(self):
  147. items = ["[%s=%s]" % key_val for key_val in self.items() if key_val[1] > 0]
  148. return "".join(items)