def parse_meta(self, lines): self.meta = OrderedDict() for key, val in self.keyvalues(lines): lang = None m = re.match(r"(.*)\[(.*)\]", key) if m: key = m.group(1) lang = m.group(2) if key not in self.meta: self.meta[key] = MultiString() self.meta[key][lang] = val
def parse_twolevel(self, lines): inner = None for line in lines: if not line: continue if line[0] == "{": if line[-1] != "}": raise ParseError("Expected {...} (%s)" % line) if inner is not None: yield name, inner name = line[1:-1] inner = OrderedDict() continue if inner is None: raise ParseError("Expected {...} (%s)" % line) if "=" not in line: raise ParseError("Expected key=value format (%s)" % line) key, val = line.split("=", 1) inner[key] = val if inner is not None: yield name, inner
def parse_variants(self, lines): self.variants = OrderedDict() for name, data in self.parse_twolevel(lines): self.variants[name] = Variant(data)
def parse_styles(self, lines): self.styles = OrderedDict() for name, data in self.parse_twolevel(lines): self.styles[name] = Style(data)
def parse_formats(self, lines): self.formats = OrderedDict() for tag, fmt in self.keyvalues(lines): if fmt not in FORMATS: raise ParseError("Unknown format %s" % fmt) self.formats[tag] = FORMATS[fmt]
def parse_song(self, lines): self.song = OrderedDict() for key, val in self.keyvalues(lines): self.song[key] = val
class Song(object): def __init__(self, filename, ignore_steps=False): parsers = { "Meta": self.parse_meta, "Song": self.parse_song, "Timing": self.parse_timing, "Formats": self.parse_formats, "Styles": self.parse_styles, "Variants": self.parse_variants, "Lyrics": self.parse_lyrics, } self.ignore_steps = ignore_steps self.pathbase = os.path.dirname(filename) section = None lines = [] self.meta = None self.song = None self.timing = None self.formats = None self.styles = None self.variants = None self.compounds = None self.fake_time = 0 self.line = 0 self.section_line = 0 for line in codecs.open(filename, encoding='utf-8', mode='r'): self.line += 1 line = line.replace("\n","").replace("\r","") if section is None: if not line: continue if line[0] != "[" or line[-1] != "]": raise ParseError("Expected section header") section = line[1:-1] self.section_line = self.line continue if line and line[0] == "[" and line[-1] == "]": if section not in parsers: raise ParseError("Unknown section %s" % section) parsers[section](lines) lines = [] section = line[1:-1] self.section_line = self.line else: lines.append(line) if section is not None: parsers[section](lines) if self.variants and self.styles: for variant in self.variants.values(): variant.load_tags(self.styles) def keyvalues(self, lines): for line in lines: if not line: continue if "=" not in line: raise ParseError("Expected key=value format (%s)" % line) key, val = line.split("=", 1) yield key, val def parse_meta(self, lines): self.meta = OrderedDict() for key, val in self.keyvalues(lines): lang = None m = re.match(r"(.*)\[(.*)\]", key) if m: key = m.group(1) lang = m.group(2) if key not in self.meta: self.meta[key] = MultiString() self.meta[key][lang] = val def parse_song(self, lines): self.song = OrderedDict() for key, val in self.keyvalues(lines): self.song[key] = val def parse_timing(self, lines): self.timing = BeatCounter() for time, beat in self.keyvalues(lines): if time[0] != "@": raise ParseError("Expected @time") self.timing.add(float(time[1:]), int(beat)) def parse_formats(self, lines): self.formats = OrderedDict() for tag, fmt in self.keyvalues(lines): if fmt not in FORMATS: raise ParseError("Unknown format %s" % fmt) self.formats[tag] = FORMATS[fmt] def parse_twolevel(self, lines): inner = None for line in lines: if not line: continue if line[0] == "{": if line[-1] != "}": raise ParseError("Expected {...} (%s)" % line) if inner is not None: yield name, inner name = line[1:-1] inner = OrderedDict() continue if inner is None: raise ParseError("Expected {...} (%s)" % line) if "=" not in line: raise ParseError("Expected key=value format (%s)" % line) key, val = line.split("=", 1) inner[key] = val if inner is not None: yield name, inner def parse_styles(self, lines): self.styles = OrderedDict() for name, data in self.parse_twolevel(lines): self.styles[name] = Style(data) def parse_variants(self, lines): self.variants = OrderedDict() for name, data in self.parse_twolevel(lines): self.variants[name] = Variant(data) def parse_lyrics(self, lines): self.compounds = [] compounds = None compound = None lineno = self.section_line for s in (lines + [""]): lineno += 1 if not s and compound: first = compound[compound.keys()[0]] for key,val in compound.items(): if first.steps != val.steps: raise ParseError("%d: Duration mismatch: %d!=%d (%s) (%s)" % (lineno, first.steps, val.steps, unicode(first), unicode(val))) if compound.timing is not None: if compound.steps != len(compound.timing): raise ParseError("%d: Timing line length mismatch: %d!=%d" % (lineno, len(compound.timing), compound.steps)) else: compound.start = self.fake_time self.fake_time += compound.steps compound = None if not s: continue if compound is None: compound = Compound(self.timing) self.compounds.append(compound) if ":" not in s: raise ParseError("Expected 'X: value': %r" % s) tag, text = s.split(":", 1) tag = tag.strip() text = text.strip() if tag == "@": if self.ignore_steps: continue timing = map(MixedFraction, text.split()) compound.start = timing[0] compound.timing = timing[1:] if compound: first = compound[compound.keys()[0]] if first.steps != len(compound.timing): raise ParseError("%d: Timing line length mismatch: %d!=%d" % (lineno, len(compound.timing), compound.steps)) continue if tag not in self.formats: raise ParseError("Undefined format %r" % tag) compound[tag] = self.formats[tag](text) def dump(self): s = "" if self.meta is not None: s += "[Meta]\n" for tag, value in self.meta.items(): for lang, text in value.items(): if lang is None: s += u"%s=%s\n" % (tag, text) else: s += u"%s[%s]=%s\n" % (tag, lang, text) s += "\n" if self.song is not None: s += "[Song]\n" for tag, value in self.song.items(): s += "%s=%s\n" % (tag, value) s += "\n" if self.timing is not None: s += "[Timing]\n" for time, beat in self.timing.beats: s += "@%f=%d\n" % (time, beat) s += "\n" if self.formats is not None: s += "[Formats]\n" for tag, fmt in self.formats.items(): s += "%s=%s\n" % (tag, I_FORMATS[fmt]) s += "\n" if self.styles is not None: s += "[Styles]\n" for name, style in self.styles.items(): s += "{%s}\n" % name for key, value in style.data.items(): s += "%s=%s\n" % (key, value) s += "\n" if self.variants is not None: s += "[Variants]\n" for name, variant in self.variants.items(): s += "{%s}\n" % name for key, value in variant.data.items(): s += u"%s=%s\n" % (key, value) s += "\n" if self.compounds is not None: s += "[Lyrics]\n\n" for compound in self.compounds: for tag, molecule in compound.items(): s += u"%s: %s\n" % (tag, molecule.source) if compound.timing is not None: s += u"@: %s %s\n" % (str(compound.start), ' '.join(map(str,compound.timing))) s += "\n" if any(i.break_before or i.break_after for i in compound.values()): s += "\n" return s def save(self, filename): fd = open(filename, "w") fd.write(self.dump().encode("utf-8")) fd.close() @property def audiofile(self): return os.path.join(self.pathbase, self.song["audio"]) @property def videofile(self): if "video" not in self.song: return None else: return os.path.join(self.pathbase, self.song["video"]) @property def coverfile(self): if "cover" not in self.song: return None else: return os.path.join(self.pathbase, self.song["cover"]) @property def aspect(self): if "aspect" not in self.song: return None else: return fractions.Fraction(self.song["aspect"]) def get_lyric_snippet(self, variant_id, length=100): variant = self.variants[variant_id] tags = set(i for i in variant.tag_list if variant.tags[i].edge == TagInfo.BOTTOM) lyrics = "" broke = False for compound in self.compounds: if len(lyrics) >= length: break for tag, molecule in compound.items(): if tag not in tags: continue if molecule.break_before and not broke: lyrics += u"/" + molecule.SPACE broke = False lyrics += molecule.text + molecule.SPACE if molecule.break_after: lyrics += u"/" + molecule.SPACE broke = True return lyrics def get_font_path(self, font): song_font = os.path.join(self.pathbase, font) if os.path.exists(song_font): return song_font cwd_font = font if os.path.exists(cwd_font): return cwd_font raise IOError("Font %s not found" % font)
def __init__(self, song_timing): OrderedDict.__init__(self) self.start = None self.timing = None self.song_timing = song_timing
class Song(object): def __init__(self, filename, ignore_steps=False): parsers = { "Meta": self.parse_meta, "Song": self.parse_song, "Timing": self.parse_timing, "Formats": self.parse_formats, "Styles": self.parse_styles, "Variants": self.parse_variants, "Lyrics": self.parse_lyrics, } self.ignore_steps = ignore_steps self.pathbase = os.path.dirname(filename) section = None lines = [] self.meta = None self.song = None self.timing = None self.formats = None self.styles = None self.variants = None self.compounds = None self.fake_time = 0 self.line = 0 self.section_line = 0 for line in codecs.open(filename, encoding='utf-8', mode='r'): self.line += 1 line = line.replace("\n", "").replace("\r", "") if line.startswith("#"): continue if section is None: if not line: continue if line[0] != "[" or line[-1] != "]": raise ParseError("Expected section header") section = line[1:-1] self.section_line = self.line continue if line and line[0] == "[" and line[-1] == "]": if section not in parsers: raise ParseError("Unknown section %s" % section) parsers[section](lines) lines = [] section = line[1:-1] self.section_line = self.line else: lines.append(line) if section is not None: parsers[section](lines) if self.variants and self.styles: for variant in self.variants.values(): variant.load_tags(self.styles) def keyvalues(self, lines): for line in lines: if not line: continue if "=" not in line: raise ParseError("Expected key=value format (%s)" % line) key, val = line.split("=", 1) yield key, val def parse_meta(self, lines): self.meta = OrderedDict() for key, val in self.keyvalues(lines): lang = None m = re.match(r"(.*)\[(.*)\]", key) if m: key = m.group(1) lang = m.group(2) if key not in self.meta: self.meta[key] = MultiString() self.meta[key][lang] = val def parse_song(self, lines): self.song = OrderedDict() for key, val in self.keyvalues(lines): self.song[key] = val def parse_timing(self, lines): self.timing = BeatCounter() for time, beat in self.keyvalues(lines): if time[0] != "@": raise ParseError("Expected @time") self.timing.add(float(time[1:]), int(beat)) def parse_formats(self, lines): self.formats = OrderedDict() for tag, fmt in self.keyvalues(lines): if fmt not in FORMATS: raise ParseError("Unknown format %s" % fmt) self.formats[tag] = FORMATS[fmt] def parse_twolevel(self, lines): inner = None for line in lines: if not line: continue if line[0] == "{": if line[-1] != "}": raise ParseError("Expected {...} (%s)" % line) if inner is not None: yield name, inner name = line[1:-1] inner = OrderedDict() continue if inner is None: raise ParseError("Expected {...} (%s)" % line) if "=" not in line: raise ParseError("Expected key=value format (%s)" % line) key, val = line.split("=", 1) inner[key] = val if inner is not None: yield name, inner def parse_styles(self, lines): self.styles = OrderedDict() for name, data in self.parse_twolevel(lines): self.styles[name] = Style(data) def parse_variants(self, lines): self.variants = OrderedDict() for name, data in self.parse_twolevel(lines): self.variants[name] = Variant(data) def parse_lyrics(self, lines): self.compounds = [] compounds = None compound = None lineno = self.section_line for s in (lines + [""]): lineno += 1 if not s and compound: first = compound[compound.keys()[0]] for key, val in compound.items(): if first.steps != val.steps: raise ParseError( "%d: Duration mismatch: %d!=%d (%s) (%s)" % (lineno, first.steps, val.steps, unicode(first), unicode(val))) if compound.timing is not None: if compound.steps != len(compound.timing): raise ParseError( "%d: Timing line length mismatch: %d!=%d" % (lineno, len(compound.timing), compound.steps)) else: compound.start = self.fake_time self.fake_time += compound.steps compound = None if not s: continue if compound is None: compound = Compound(self.timing) self.compounds.append(compound) if ":" not in s: raise ParseError("Expected 'X: value': %r" % s) tag, text = s.split(":", 1) tag = tag.strip() text = text.strip() if tag == "@": if self.ignore_steps: continue timing = map(MixedFraction, text.split()) compound.start = timing[0] compound.timing = timing[1:] if compound: first = compound[compound.keys()[0]] if first.steps != len(compound.timing): raise ParseError( "%d: Timing line length mismatch: %d!=%d" % (lineno, len(compound.timing), compound.steps)) continue if tag not in self.formats: raise ParseError("Undefined format %r" % tag) compound[tag] = self.formats[tag](text) def dump(self): s = "" if self.meta is not None: s += "[Meta]\n" for tag, value in self.meta.items(): for lang, text in value.items(): if lang is None: s += u"%s=%s\n" % (tag, text) else: s += u"%s[%s]=%s\n" % (tag, lang, text) s += "\n" if self.song is not None: s += "[Song]\n" for tag, value in self.song.items(): s += "%s=%s\n" % (tag, value) s += "\n" if self.timing is not None: s += "[Timing]\n" for time, beat in self.timing.beats: s += "@%f=%d\n" % (time, beat) s += "\n" if self.formats is not None: s += "[Formats]\n" for tag, fmt in self.formats.items(): s += "%s=%s\n" % (tag, I_FORMATS[fmt]) s += "\n" if self.styles is not None: s += "[Styles]\n" for name, style in self.styles.items(): s += "{%s}\n" % name for key, value in style.data.items(): s += "%s=%s\n" % (key, value) s += "\n" if self.variants is not None: s += "[Variants]\n" for name, variant in self.variants.items(): s += "{%s}\n" % name for key, value in variant.data.items(): s += u"%s=%s\n" % (key, value) s += "\n" if self.compounds is not None: s += "[Lyrics]\n\n" for compound in self.compounds: for tag, molecule in compound.items(): s += u"%s: %s\n" % (tag, molecule.source) if compound.timing is not None: s += u"@: %s %s\n" % (str(compound.start), ' '.join( map(str, compound.timing))) s += "\n" if any(i.break_before or i.break_after for i in compound.values()): s += "\n" return s def save(self, filename): fd = open(filename, "w") fd.write(self.dump().encode("utf-8")) fd.close() @property def audiofile(self): return os.path.join(self.pathbase, self.song["audio"]) @property def videofile(self): if "video" not in self.song: return None else: return os.path.join(self.pathbase, self.song["video"]) @property def coverfile(self): if "cover" not in self.song: return None else: return os.path.join(self.pathbase, self.song["cover"]) @property def aspect(self): if "aspect" not in self.song: return None else: return fractions.Fraction(self.song["aspect"]) def get_lyric_snippet(self, variant_id, length=100): variant = self.variants[variant_id] tags = set(i for i in variant.tag_list if variant.tags[i].edge == TagInfo.BOTTOM) lyrics = "" broke = False for compound in self.compounds: if len(lyrics) >= length: break for tag, molecule in compound.items(): if tag not in tags: continue if molecule.break_before and not broke: lyrics += u"/" + molecule.SPACE broke = False lyrics += molecule.text + molecule.SPACE if molecule.break_after: lyrics += u"/" + molecule.SPACE broke = True return lyrics def get_font_path(self, font): song_font = os.path.join(self.pathbase, font) if os.path.exists(song_font): return song_font cwd_font = font if os.path.exists(cwd_font): return cwd_font raise IOError("Font %s not found" % font)
def __init__(self, filename=None, ignore_steps=False): parsers = { "Meta": self.parse_meta, "Song": self.parse_song, "Timing": self.parse_timing, "Formats": self.parse_formats, "Styles": self.parse_styles, "Variants": self.parse_variants, "Lyrics": self.parse_lyrics, } self.ignore_steps = ignore_steps self.pathbase = os.path.dirname(filename) if filename else None section = None lines = [] self.meta = None self.song = None self.timing = None self.formats = None self.styles = None self.variants = None self.compounds = None self.fake_time = 0 self.line = 0 self.section_line = 0 if not filename: self.meta = OrderedDict() self.song = OrderedDict() self.timing = BeatCounter() self.formats = OrderedDict() self.styles = OrderedDict() self.variants = OrderedDict() self.compounds = [] return for line in codecs.open(filename, encoding='utf-8', mode='r'): self.line += 1 line = line.replace("\n","").replace("\r","") if line.startswith("#"): continue if section is None: if not line: continue if line[0] != "[" or line[-1] != "]": raise ParseError("Expected section header") section = line[1:-1] self.section_line = self.line continue if line and line[0] == "[" and line[-1] == "]": if section not in parsers: raise ParseError("Unknown section %s" % section) parsers[section](lines) lines = [] section = line[1:-1] self.section_line = self.line else: lines.append(line) if section is not None: parsers[section](lines) if self.variants and self.styles: for variant in self.variants.values(): variant.load_tags(self.styles)