def main_render(): # Wait for element in queue print("Waiting for song to appear in queue...") qe = None with queue.lock: if len(queue) != 0: qe = queue[0] if not qe: idle_screen.reset() graphics.get_renderer().clear(0, 0, 0, 1) for f in idle_screen: audio_config.update() yield None graphics.get_renderer().clear(0, 0, 0, 1) if not qe: with queue.lock: if len(queue) != 0: qe = queue[0] idle_screen.close() for i in range(2): yield None graphics.get_renderer().clear(0, 0, 0, 1) print("Loading audio/video...") mpv.load_song(qe.song) display.set_aspect(mpv.aspect) print("Laying out song...") renderer.reset() variant_key = list(qe.song.variants.keys())[qe.variant] song_layout = layout.SongLayout(qe.song, variant_key, renderer) print("Loaded.") def update_params(): mpv.set_speed(1.0 / (2**(qe.speed / 12.0))) mpv.set_pitch(2**(qe.pitch / 12.0)) for i, j in enumerate(qe.channels): mpv.set_channel(i, j["volume"] / 10.0) mpv.set_pause(qe.pause) update_params() song_time = -10 stopping = False while not (mpv.eof_reached() or (stopping and qe.pause)): while qe.commands: cmd, arg = qe.commands.pop(0) if cmd == "seek": mpv.seek(arg) elif cmd == "seekto": mpv.seek_to(arg) mpv.draw() mpv.poll() song_time = mpv.get_song_time() or song_time if qe.stop and not stopping: stopping = True mpv.fade_out = 2 mpv.duration = min(mpv.duration, song_time + 2) if not stopping: mpv.draw_fade(song_time) speed = 2**(qe.speed / 12.0) renderer.draw(song_time + audio_config.headstart / 100.0 * speed, song_layout) if stopping: fade = mpv.draw_fade(song_time) mpv.set_fadevol(max(fade * 1.3 - 0.3, 0)) update_params() audio_config.update(qe.song) yield None mpv.flip() graphics.get_renderer().clear(0, 0, 0, 1) for i in range(2): yield None graphics.get_renderer().clear(0, 0, 0, 1) print("Song complete.") try: queue.pop(qe.qid) except (IndexError, KeyError): pass mpv.stop() display.set_aspect(None)
'ffmpeg_opts', metavar='OPTS', nargs=argparse.REMAINDER, help='ffmpeg options') opts = util.get_opts() opts.display = "surfaceless" opts.mpv_ao = "null" if not opts.video: opts.mpv_vo = "null" s = song.Song(opts.songpath) display = graphics.Display(opts.width, opts.height) import OpenGL.GLES3 as gl renderer = graphics.get_renderer().KaraokeRenderer(display) layout = layout.SongLayout(s, list(s.variants.keys())[opts.variant], renderer) # Get duration of the audio path mpv = mpvplayer.Player(display) mpv.load_song(s) duration = mpv.duration or mpv.file_duration # Now run for rendering using only video mpv = mpvplayer.Player(display, rendering=True) mpv.load_song(s) if not opts.video: mpv.shutdown() print("Song duration: %f" % duration) if opts.length: duration = min(duration, opts.length)
def entry(): parser = util.get_argparser() parser.add_argument('songpath', metavar='SONGPATH', help='path to the song file') parser.add_argument('--show-timings', dest='st', action='store_true', help='show mpv timings') parser.add_argument('--offset', type=float, default=0.0, help='song offset') parser.add_argument('--variant', type=int, default=0, help='song variant') opts = util.get_opts() fullscreen = opts.fullscreen s = song.Song(opts.songpath) headstart = 0.3 if fullscreen: display = graphics.Display(1920, 1200, fullscreen, None) else: display = graphics.Display(1280, 720, fullscreen, None) print(display.width, display.height) mpv = mpvplayer.Player(display) mpv.load_song(s) display.set_aspect(mpv.aspect) renderer = graphics.get_renderer().KaraokeRenderer(display) song_layout = layout.SongLayout(s, list(s.variants.keys())[opts.variant], renderer) song_time = -10 speed_i = 0 pitch_i = 0 channels_i = s.channel_defaults for idx, val in enumerate(channels_i): mpv.set_channel(idx, val / 10.0) if opts.offset: mpv.seek_to(opts.offset) def render(): t = time.time() nonlocal song_time while not mpv.eof_reached(): graphics.get_renderer().clear(0, 0, 0, 1) t1 = time.time() mpv.draw() dt = time.time() - t1 mpv.poll() song_time = mpv.get_song_time() or song_time mpv.draw_fade(song_time) renderer.draw(song_time + headstart * 2**(speed_i / 12.0), song_layout) yield None t2 = time.time() if opts.st: print("T:%7.3f/%7.3f B:%7.3f FPS:%.2f draw:%.3f" % (song_time, mpv.duration, s.timing.time2beat(song_time), (1.0 / (t2 - t)), dt)) t = t2 mpv.flip() mpv.shutdown() os._exit(0) pause = False CH_UP = "+456" CH_DOWN = "-123" def key(k): nonlocal speed_i, pitch_i, pause if k == 'KEY_ESCAPE': mpv.shutdown() os._exit(0) elif k == 'f': display.toggle_fullscreen() elif k == '[' and speed_i > -12: speed_i -= 1 print("Speed: %d" % speed_i) mpv.set_speed(2**(-speed_i / 12.0)) elif k == ']' and speed_i < 12: speed_i += 1 print("Speed: %d" % speed_i) mpv.set_speed(2**(-speed_i / 12.0)) elif k == 'KEY_UP' and pitch_i < 12: pitch_i += 1 print("Pitch: %d" % pitch_i) mpv.set_pitch(2**(pitch_i / 12.0)) elif k == 'KEY_DOWN' and pitch_i > -12: pitch_i -= 1 print("Pitch: %d" % pitch_i) mpv.set_pitch(2**(pitch_i / 12.0)) elif k in CH_UP: idx = CH_UP.index(k) if len(channels_i) > idx and channels_i[idx] < 30: channels_i[idx] += 1 print("Channel %d: %d" % (idx, channels_i[idx])) mpv.set_channel(idx, channels_i[idx] / 10.0) elif k in CH_DOWN: idx = CH_DOWN.index(k) if len(channels_i) > idx and channels_i[idx] > 0: channels_i[idx] -= 1 print("Channel %d: %d" % (idx, channels_i[idx])) mpv.set_channel(idx, channels_i[idx] / 10.0) elif k == 'KEY_LEFT': mpv.seek(-10) elif k == 'KEY_RIGHT': mpv.seek(10) elif k == ' ': pause = not pause t = time.time() mpv.set_pause(pause) print("P %.03f" % (time.time() - t)) mpv.play() display.set_render_gen(render) display.set_keyboard_handler(key) display.main_loop() mpv.shutdown()
def _load_lyrics(self, s): telop = self.root.find("telop") renderer = graphics.get_renderer().KaraokeRenderer(self.display) lyt = layout.SongLayout(s, list(s.variants.keys())[opts.variant], renderer) lines = lyt.lines[song.TagInfo.BOTTOM] class Page(object): pass def ms(i): return str(int(round(i * 1000))) # Group lines into pages pages = [] page = Page() page.lines = {} last = None for l in lines: if l.row in page.lines or (last and (l.row > last.row or l.start > last.end)): pages.append(page) page = Page() page.lines = {} page.lines[l.row] = l last = l pages.append(page) # Compute page show/hide times prev = None pages2 = [] for page in pages: lines = page.lines page.start = min(l.start for l in lines.values()) page.min_start = min(l._start_t for l in lines.values()) page.end = max(l.end for l in lines.values()) page.min_end = max(l._end_t for l in lines.values()) if prev and page.start < prev.end: page.start = min(page.min_start, prev.end) if prev and page.start < prev.end: prev.end = max(prev.min_end, page.start) if prev and page.start < prev.end: li = " ".join( repr(v.molecules[0][0].text) for k, v in sorted(lines.items(), reverse=True)) raise Exception("overlapping lines! %r" % (li)) prev = page pages2.append(page) # Generate XML for page in pages: pe = ET.Element("page") pe.append(self._tag("show_time", ms(page.start))) pe.append(self._tag("hide_time", ms(page.end))) pe.append(self._tag("paint_timing", "0")) pe.append(self._tag("layout", "xing_0")) t = page.min_start for i in range(max(page.lines.keys()), -1, -1): le = ET.Element("line") text = "" if i not in page.lines: # dummy line w = ET.Element("word") w.append(self._tag("text", "")) w.append(self._tag("start_time", ms(t))) w.append(self._tag("end_time", ms(t))) le.append(w) else: line = page.lines[i] assert len(line.molecules) == 1 mol, get_atom_time = line.molecules[0] ruby = [] step = 0 for atom in mol.atoms: start, end = get_atom_time(step, atom.steps) step += atom.steps w = ET.Element("word") w.append(self._tag("text", atom.text)) w.append(self._tag("start_time", ms(start))) w.append(self._tag("end_time", ms(end))) le.append(w) if atom.particles is not None: edge = len(atom.text) if atom.particle_edge: edge = atom.particle_edge rt = "" for i in atom.particles: rt += i.text r = ET.Element("ruby") r.append(self._tag("text", rt)) r.append(self._tag("start_pos", "%d" % len(text))) r.append( self._tag("end_pos", "%d" % (len(text) + edge - 1))) ruby.append(r) text += atom.text for r in ruby: le.append(r) print(text) e = ET.Element("duet") e.append(self._tag("mark", "0")) e.append(self._tag("start_pos", "0")) e.append(self._tag("end_pos", "%d" % (len(text) - 1))) le.append(e) e = ET.Element("offset") e.append(self._tag("type", "0")) e.append(self._tag("offset", "0")) le.append(e) pe.append(le) telop.append(pe)
def _load_lyrics(self, s): telop = self.root.find("telop") renderer = graphics.get_renderer().KaraokeRenderer(self.display) lyt = layout.SongLayout(s, list(s.variants.keys())[opts.variant], renderer) lines = lyt.lines[song.TagInfo.BOTTOM] class Page(object): pass def ms(i): return str(int(round(i * 1000))) def color(rgb): r, g, b = rgb return "%d" % (r | (g << 8) | (b << 16)) # Group lines into pages pages = [] page = Page() page.lines = {} last = None for l in lines: if l.row in page.lines or (last and (l.row > last.row or l.start > last.end)): pages.append(page) page = Page() page.lines = {} page.lines[l.row] = l last = l pages.append(page) # Compute page show/hide times prev = None pages2 = [] for page in pages: lines = page.lines page.start = min(l.start for l in lines.values()) page.min_start = min(l._start_t for l in lines.values()) page.end = max(l.end for l in lines.values()) page.min_end = max(l._end_t for l in lines.values()) if prev and page.start < prev.end: page.start = min(page.min_start, prev.end) if prev and page.start < prev.end: prev.end = max(prev.min_end, page.start) if prev and page.start < prev.end: li = " ".join( repr(v.molecules[0][0].text) for k, v in sorted(lines.items(), reverse=True)) raise Exception("overlapping lines! %r" % (li)) prev = page pages2.append(page) # Generate XML for page in pages: pe = ET.Element("page") pe.append(self._tag("show_time", ms(page.start))) pe.append(self._tag("hide_time", ms(page.end))) pe.append(self._tag("paint_timing", "0")) pe.append(self._tag("layout", "xing_0")) t = page.min_start styles = None for i in range(max(page.lines.keys()), -1, -1): le = ET.Element("line") text = "" if i not in page.lines: if styles: styles = [[styles[-1][0], 0, 0]] else: styles = [[ page.lines.values()[0].molecules[0].style, 0, 0 ]] # dummy line w = ET.Element("word") w.append(self._tag("text", " ")) w.append(self._tag("start_time", ms(t))) w.append(self._tag("end_time", ms(t))) le.append(w) text = " " width = cwidth = 0 align = 0 else: line = page.lines[i] align = line.align ruby = [] styles = [] for idx, instance in enumerate(line.molecules): mol = instance.molecule step = 0 start_pos = len(text) for atom in mol.atoms: start, end = instance.get_atom_time( step, atom.steps) t = max(t, end) step += atom.steps w = ET.Element("word") w.append(self._tag("text", atom.text)) w.append(self._tag("start_time", ms(start))) w.append(self._tag("end_time", ms(end))) le.append(w) if atom.particles is not None: edge = len(atom.text) edge_l = 0 if atom.particle_edge: edge = atom.particle_edge if atom.particle_edge_l: edge_l = atom.particle_edge_l rt = "" for i in atom.particles: rt += i.text r = ET.Element("ruby") r.append(self._tag("text", rt)) r.append( self._tag("start_pos", "%d" % (len(text) + edge_l))) r.append( self._tag("end_pos", "%d" % (len(text) + edge - 1))) ruby.append(r) text += atom.text if idx != (len(line.molecules) - 1): text += " " w[0].text += " " if styles and instance.style == styles[-1]: styles[-1][2] = len(text) - 1 else: styles.append( [instance.style, start_pos, len(text) - 1]) for r in ruby: le.append(r) print(text) width = line.max_px - line.min_px cwidth = len(text) for i in text: ew = unicodedata.east_asian_width(i) if ew in ("F", "W", "A"): cwidth += 1 if cwidth > 26: print(" ^-- WARNING: Line likely too long") for style, start, end in styles: e = ET.Element("color") e.append( self._tag("before_text_color", color(style.colors[0]))) e.append( self._tag("after_text_color", color(style.colors_on[0]))) e.append( self._tag("before_shadow_color", color(style.colors[1]))) e.append( self._tag("after_shadow_color", color(style.colors_on[1]))) e.append( self._tag("before_text_no_fill_color", color(style.colors[0]))) e.append( self._tag("after_text_no_fill_color", color(style.colors_on[0]))) e.append( self._tag("before_shadow_no_fill_color", color(style.colors[1]))) e.append( self._tag("after_shadow_no_fill_color", color(style.colors_on[1]))) e.append(self._tag("start_pos", "%d" % start)) e.append(self._tag("end_pos", "%d" % end)) e.append( self._tag( "no_fill", "1" if style.colors == style.colors_on else "0")) le.append(e) e = ET.Element("duet") e.append(self._tag("mark", "0")) e.append(self._tag("start_pos", "0")) e.append(self._tag("end_pos", "%d" % (len(text) - 1))) le.append(e) e = ET.Element("offset") w = 720 margin = 54 offset = margin + (w * (1 - width) - 2 * margin) * align if offset < 0 or offset + w * width > w - margin: print(" ^-- WARNING: Line too wide") # 0 = default # 1 = offset from default # 2 = abs left side e.append(self._tag("type", "2")) e.append(self._tag("auto_pos", "0")) e.append(self._tag("offset", "%d" % round(offset))) le.append(e) pe.append(le) telop.append(pe) self.root.remove(telop) self.root.append(telop) telop_edit_setting = ET.XML(""" <telop_edit_setting> <music_volume>50</music_volume> <vocal_volume>50</vocal_volume> </telop_edit_setting> """.replace("\n", "").replace(" ", "")) self.root.append(telop_edit_setting)