def _render(self): self.matrix.reset() self.gl.glClearColor(*self.clear_color) self.gl.glClear(self.gl.GL_COLOR_BUFFER_BIT | self.gl.GL_DEPTH_BUFFER_BIT) next(self.frames) graphics.get_renderer().cleanup()
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)
def render(): t = time.time() global 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), 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)
def __init__(self, display): self.display = display tr = graphics.get_texture_renderer() ImageTexture = graphics.get_renderer().ImageTexture self.logo = ImageTexture(util.get_resgfx_path("logo.png"), tr) self.tablet = ImageTexture(util.get_resgfx_path("tablet.png"), tr) self.hand = ImageTexture(util.get_resgfx_path("hand.png"), tr) self.silhouette = ImageTexture(util.get_resgfx_path("silhouette.png"), tr) self.reset()
def entry(): data_home = os.getenv('XDG_DATA_HOME', '~/.local/share') songs_dir = os.path.join(data_home, 'blitzloop', 'songs') def csv_list(s): return s.split(",") parser = util.get_argparser() parser.add_argument( '--songdir', default=os.path.expanduser(songs_dir), help='directory with songs') parser.add_argument('--host', default='0.0.0.0', help='IP to listen on') parser.add_argument( '--port', default=10111, type=int, help='port for the UI') parser.add_argument( '--width', type=int, default=1024, help='width of blitzloop window (ignored in fs)') parser.add_argument( '--height', type=int, default=768, help='height of blitzloop window (ignored in fs)') parser.add_argument( '--no-audioengine', action="store_true", help='Disable JACK-based audio engine (mic echo effect)') parser.add_argument( '--mics', type=csv_list, default=["system:capture_1"], help='Mic input connections (list of JACK ports)') opts = util.get_opts() songs_dir = os.path.expanduser(opts.songdir) print("Loading song DB...") song_database = songlist.SongDatabase(songs_dir) print("Done.") display = graphics.Display(opts.width, opts.height, opts.fullscreen) renderer = graphics.get_renderer().KaraokeRenderer(display) mpv = mpvplayer.Player(display) if not opts.no_audioengine and opts.mics: from blitzloop._audio import AudioEngine print(repr(opts.mics)) audio = AudioEngine([s.encode("ascii") for s in opts.mics]) print("Engine sample rate: %dHz" % audio.sample_rate) queue = songlist.SongQueue() class AudioConfig(object): def __init__(self): self.nmics = len(opts.mics) if not opts.no_audioengine else 0 self.volume = 80 if opts.no_audioengine: self.mic_channels = [] else: self.mic_channels = [{"volume": 80} for i in range(self.nmics)] self.mic_feedback = 20 self.mic_delay = 12 self.headstart = 30 def update(self, song=None): mpv.set_volume(((self.volume / 100.0) ** 2) * 0.5) if not opts.no_audioengine: for i, j in enumerate(self.mic_channels): audio.set_mic_volume(i, ((j["volume"] / 100.0) ** 2) * 2.0) audio.set_mic_feedback(self.mic_feedback / 100.0) audio.set_mic_delay(self.mic_delay / 100.0) audio_config = AudioConfig() web.database = song_database web.queue = queue web.audio_config = audio_config server = web.ServerThread(host=opts.host, port=opts.port) server.start() idle_screen = idlescreen.IdleScreen(display) 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) def update_params(defer=False): 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., defer=defer) mpv.set_pause(qe.pause) update_params(True) mpv.update_mixer(force=True) 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.") 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.") del song_layout try: queue.pop(qe.qid) except (IndexError, KeyError): pass mpv.stop() display.set_aspect(None) def main(): while True: for i in main_render(): yield i def exit(): print("Exit handler called") mpv.shutdown() if not opts.no_audioengine: audio.shutdown() server.stop() print("Exit handler done") def key(k): if k == 'KEY_ESCAPE': display.queue_exit() elif k == 'f': display.toggle_fullscreen() display.set_render_gen(main) display.set_keyboard_handler(key) display.set_exit_handler(exit) display.main_loop() threads = threading.enumerate() if len(threads) > 1: print("Loose threads: %r" % threads)
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) def update_params(defer=False): 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., defer=defer) mpv.set_pause(qe.pause) update_params(True) mpv.update_mixer(force=True) 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.") 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.") del song_layout try: queue.pop(qe.qid) except (IndexError, KeyError): pass mpv.stop() display.set_aspect(None)
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)
action="store_true", help='Disable JACK-based audio engine (mic echo effect)') parser.add_argument('--mics', type=csv_list, default=["system:capture_1"], help='Mic input connections (list of JACK ports)') opts = util.get_opts() songs_dir = os.path.expanduser(opts.songdir) print("Loading song DB...") song_database = songlist.SongDatabase(songs_dir) print("Done.") display = graphics.Display(opts.width, opts.height, opts.fullscreen) renderer = graphics.get_renderer().KaraokeRenderer(display) mpv = mpvplayer.Player(display) if not opts.no_audioengine and opts.mics: print(repr(opts.mics)) audio = AudioEngine([s.encode("ascii") for s in opts.mics]) print("Engine sample rate: %dHz" % audio.sample_rate) queue = songlist.SongQueue() class AudioConfig(object): def __init__(self): self.nmics = len(opts.mics) if not opts.no_audioengine else 0 self.volume = 80 if opts.no_audioengine:
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 build(self): self.rline = get_renderer().RenderedLine(self) self.rline.build()
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 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()
parser = util.get_argparser() parser.add_argument( 'songpath', metavar='SONGPATH', help='path to the song file') parser.add_argument( 'quant', metavar='QUANT', type=int, help='quantization of the song') parser.add_argument( '--speed', default=1.0, type=float, help='divisor of the audio speed') parser.add_argument( '--position', default=0.0, type=float, help='starting position in the song, in seconds') opts = util.get_opts() s = song.Song(opts.songpath, ignore_steps=True) display = graphics.Display(1280,720) renderer = graphics.get_renderer().KaraokeRenderer(display) layout = layout.SongLayout(s, list(s.variants.keys())[-1], renderer) step = 0 cur_beat = 0 compound = None compounds = iter(s.compounds) mpv = mpvplayer.Player(None) mpv.load_song(s) mpv.set_speed(opts.speed) if opts.position > 0.0: mpv.seek_to(opts.position)
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)
def entry(): data_home = os.getenv('XDG_DATA_HOME', '~/.local/share') songs_dir = os.path.join(data_home, 'blitzloop', 'songs') def csv_list(s): return s.split(",") parser = util.get_argparser() parser.add_argument('--songdir', default=os.path.expanduser(songs_dir), help='directory with songs') parser.add_argument('--host', default='0.0.0.0', help='IP to listen on') parser.add_argument('--port', default=10111, type=int, help='port for the UI') parser.add_argument('--width', type=int, default=1024, help='width of blitzloop window (ignored in fs)') parser.add_argument('--height', type=int, default=768, help='height of blitzloop window (ignored in fs)') parser.add_argument( '--no-audioengine', action="store_true", help='Disable JACK-based audio engine (mic echo effect)') parser.add_argument('--mics', type=csv_list, default=["system:capture_1"], help='Mic input connections (list of JACK ports)') opts = util.get_opts() songs_dir = os.path.expanduser(opts.songdir) print("Loading song DB...") song_database = songlist.SongDatabase(songs_dir) print("Done.") display = graphics.Display(opts.width, opts.height, opts.fullscreen) renderer = graphics.get_renderer().KaraokeRenderer(display) mpv = mpvplayer.Player(display) if not opts.no_audioengine and opts.mics: from blitzloop._audio import AudioEngine print(repr(opts.mics)) audio = AudioEngine([s.encode("ascii") for s in opts.mics]) print("Engine sample rate: %dHz" % audio.sample_rate) queue = songlist.SongQueue() class AudioConfig(object): def __init__(self): self.nmics = len(opts.mics) if not opts.no_audioengine else 0 self.volume = 80 if opts.no_audioengine: self.mic_channels = [] else: self.mic_channels = [{"volume": 80} for i in range(self.nmics)] self.mic_feedback = 20 self.mic_delay = 12 self.headstart = 30 def update(self, song=None): mpv.set_volume(((self.volume / 100.0)**2) * 0.5) if not opts.no_audioengine: for i, j in enumerate(self.mic_channels): audio.set_mic_volume(i, ((j["volume"] / 100.0)**2) * 2.0) audio.set_mic_feedback(self.mic_feedback / 100.0) audio.set_mic_delay(self.mic_delay / 100.0) audio_config = AudioConfig() web.database = song_database web.queue = queue web.audio_config = audio_config server = web.ServerThread(host=opts.host, port=opts.port) server.start() idle_screen = idlescreen.IdleScreen(display) 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) def update_params(defer=False): 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., defer=defer) mpv.set_pause(qe.pause) update_params(True) mpv.update_mixer(force=True) 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.") 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.") del song_layout try: queue.pop(qe.qid) except (IndexError, KeyError): pass mpv.stop() display.set_aspect(None) def main(): while True: for i in main_render(): yield i def exit(): print("Exit handler called") mpv.shutdown() if not opts.no_audioengine: audio.shutdown() server.stop() print("Exit handler done") def key(k): if k == 'KEY_ESCAPE': display.queue_exit() elif k == 'f': display.toggle_fullscreen() display.set_render_gen(main) display.set_keyboard_handler(key) display.set_exit_handler(exit) display.main_loop() threads = threading.enumerate() if len(threads) > 1: print("Loose threads: %r" % threads)