def run_command(args: List[str], progress: Progress, start: int = 0, duration: Optional[int] = None) -> None: """ Start a new process running specified command and wait for it to complete. :duration: If not None, the progress percentage will be updated based upon the amount of time the process has been running. Can be terminated by setting progress.abort to True """ progress.pct = 0.0 start_time = time.time() with subprocess.Popen(args) as proc: done = False while not done and not progress.abort: rcode = proc.poll() if rcode is not None: done = True proc.wait() else: if duration is not None: elapsed = min(duration, 1000.0 * (time.time() - start_time)) progress.pct = 100.0 * elapsed / float(duration) progress.pct_text = Duration(int(elapsed) + start).format() time.sleep(0.25) if progress.abort: proc.terminate() proc.wait() progress.pct = 100.0
def _update_footer(self): """ Update the duration text at the bottom of the treeview. This function is called after any addition or removal of songs to/from the lists. """ txt = self.FOOTER_TEMPLATE.format(num_songs=self._num_songs, duration=Duration( self._duration).format()) self.footer.config(text=txt)
def run(self, songs: List[Song]) -> None: # type: ignore """ Play one or more songs This function runs in its own thread """ mp3editor = MP3Factory.create_editor() for song in songs: afile = mp3editor.use(song) if self.options.mode == GameMode.CLIP: start = int(Duration.parse(self.options.clip_start)) if start >= int(song.duration): continue end = start + self.options.clip_duration * 1000 afile = afile.clip(start, end) self.progress.text = f'{song.artist}: {song.title}' mp3editor.play(afile, self.progress) if self.progress.abort: return
def generate_mp3(self) -> List[Song]: """ Generate the mp3 for the game with the generated order of tracks. Returns a list of songs with the start_time metadata property of each song set to their positon in the output. """ songs = self.gen_track_order() assert len(songs) > 0 mp3_name = self.options.mp3_output_name() album: str = '' albums: Set[str] = set() channels: List[int] = [] sample_width: List[int] = [] sample_rate: List[int] = [] for song in songs: channels.append(song.channels) sample_width.append(song.sample_width) sample_rate.append(song.sample_rate) if song.album: albums.add(song.album) if len(albums) == 1: album = list(albums)[0] metadata = Metadata( title=f'{self.options.game_id} - {self.options.title}', artist='', album=album, channels=int(statistics.median(channels)), sample_rate=int(statistics.median(sample_rate)), sample_width=int(statistics.median(sample_width)), bitrate=self.options.bitrate, duration=Duration(0), ) with self.mp3_editor.create(mp3_name, metadata=metadata, progress=self.progress) as output: tracks = self.append_songs(output, songs) self.save_game_tracks_json(tracks) return tracks
def _update_footer(self): """ Update the duration text at the bottom of the panel. This function is called after any addition or removal of songs to/from the lists. """ if self._num_songs < 30: box_col = "#ff0000" elif self._num_songs < 45: box_col = "#fffa20" else: box_col = "#00c009" if self.options.mode == GameMode.CLIP: mode = "to clip" elif self.options.mode == GameMode.QUIZ: mode = "in quiz" else: mode = "in game" txt = self.FOOTER_TEMPLATE.format(mode=mode, num_songs=self._num_songs, duration=Duration( self._duration).format()) self.footer.config(text=txt, fg=box_col)
def generate(self, songs: List[Song]) -> List[Path]: """ Generate all clips for all selected Songs Returns list of filenames of new clips """ total_songs = len(songs) clips: List[Path] = [] start = int(Duration(self.options.clip_start)) end = start + 1000 * self.options.clip_duration for index, song in enumerate(songs): self.progress.text = '{} ({:d}/{:d})'.format( Song.clean(song.title), index, total_songs) self.progress.pct = 100.0 * float(index) / float(total_songs) #pylint: disable=broad-except try: clips.append(self.generate_clip(song, start, end)) except (InvalidMP3Exception, ValueError) as err: traceback.print_exc() print(r'Error generating clip: {0} - {1}'.format( Song.clean(song.title), str(err))) self.progress.pct = 100.0 self.progress.text = 'Finished generating clips' return clips
def generate_card_results(self, tracks: List[Song], cards: List[BingoTicket]): """generate PDF showing when each ticket wins""" doc = DG.Document( pagesize=PageSizes.A4, title=f'{self.options.game_id} - {self.options.title}', topMargin="0.25in", bottomMargin="0.25in", rightMargin="0.25in", leftMargin="0.25in") doc.append(self.options.palette.logo_image("6.2in")) doc.append(DG.Spacer(width=0, height="0.05in")) doc.append( DG.Paragraph( f'Results For Game Number: <b>{self.options.game_id}</b>', self.TEXT_STYLES['results-heading'])) doc.append( DG.Paragraph(self.options.title, self.TEXT_STYLES['results-title'])) pstyle = self.TEXT_STYLES['results-cell'] heading: DG.TableRow = [ DG.Paragraph('<b>Ticket Number</b>', pstyle), DG.Paragraph('<b>Wins after track</b>', pstyle), DG.Paragraph('<b>Start Time</b>', pstyle), ] data: List[DG.TableRow] = [] cards = copy.copy(cards) cards.sort(key=lambda card: card.ticket_number, reverse=False) for card in cards: win_point = self.get_when_ticket_wins(tracks, card) song = tracks[win_point - 1] data.append([ DG.Paragraph(f'{card.ticket_number}', pstyle), DG.Paragraph( f'Track {win_point} - {song.title} ({song.artist})', pstyle), DG.Paragraph(Duration(song.start_time).format(), pstyle) ]) col_widths: List[Dimension] = [ Dimension("0.75in"), Dimension("5.5in"), Dimension("0.85in"), ] hstyle = pstyle.replace(name='results-table-heading', background=self.options.palette.title_bg) tstyle = TableStyle(name='results-table', borderColour=Colour('black'), borderWidth=1.0, gridColour=Colour('black'), gridWidth=0.5, verticalAlignment=VerticalAlignment.CENTER, headingStyle=hstyle) table = DG.Table(data, heading=heading, repeat_heading=True, colWidths=col_widths, style=tstyle) doc.append(table) filename = str(self.options.ticket_results_output_name()) self.doc_gen.render(filename, doc, Progress())
def generate_track_listing(self, tracks: List[Song]) -> None: """generate a PDF version of the track order in the game""" assert len(tracks) > 0 doc = DG.Document( PageSizes.A4, topMargin="0.25in", bottomMargin="0.25in", leftMargin="0.35in", rightMargin="0.35in", title=f'{self.options.game_id} - {self.options.title}') doc.append(self.options.palette.logo_image("6.2in")) doc.append(DG.Spacer(width=0, height="0.05in")) doc.append( DG.Paragraph( f'Track Listing For Game Number: <b>{self.options.game_id}</b>', self.TEXT_STYLES['track-heading'])) doc.append( DG.Paragraph(self.options.title, self.TEXT_STYLES['track-title'])) cell_style = self.TEXT_STYLES['track-cell'] heading: DG.TableRow = [ DG.Paragraph('<b>Order</b>', cell_style), DG.Paragraph('<b>Title</b>', cell_style), DG.Paragraph('<b>Artist</b>', cell_style), DG.Paragraph('<b>Start Time</b>', cell_style), DG.Paragraph('', cell_style), ] data: List[DG.TableRow] = [] for index, song in enumerate(tracks, start=1): order = DG.Paragraph(f'<b>{index}</b>', cell_style) title = DG.Paragraph(song.title, cell_style) if self.should_include_artist(song): artist = DG.Paragraph(song.artist, cell_style) else: artist = DG.Paragraph('', cell_style) start = DG.Paragraph( Duration(song.start_time).format(), cell_style) end_box = DG.Paragraph('', cell_style) data.append([order, title, artist, start, end_box]) col_widths = [ Dimension("0.55in"), Dimension("2.9in"), Dimension("2.9in"), Dimension("0.85in"), Dimension("0.2in") ] hstyle = cell_style.replace(name='track-table-heading', background=self.options.palette.title_bg) tstyle = TableStyle(name='track-table', borderColour=Colour('black'), borderWidth=1.0, gridColour=Colour('black'), gridWidth=0.5, verticalAlignment=VerticalAlignment.CENTER, headingStyle=hstyle) table = DG.Table(data, heading=heading, repeat_heading=True, colWidths=col_widths, style=tstyle) doc.append(table) filename = str(self.options.track_listing_output_name()) self.doc_gen.render(filename, doc, Progress())
def append_songs(self, output: MP3FileWriter, songs: List[Song]) -> List[Song]: """ Append all of the songs to the specified output. Returns a new song list with the start_time metadata property of each song set to their positon in the output. """ sample_rate = output.metadata.sample_rate transition = self.mp3_editor.use(Assets.transition(sample_rate)) if self.options.mode == GameMode.QUIZ: countdown = self.mp3_editor.use(Assets.quiz_countdown(sample_rate)) else: countdown = self.mp3_editor.use(Assets.countdown(sample_rate)) overlap: Optional[Duration] = None if self.options.crossfade > 0: overlap = Duration(self.options.crossfade) if self.options.mode == GameMode.QUIZ: start, end = Assets.QUIZ_COUNTDOWN_POSITIONS['1'] output.append(countdown.clip(start, end)) else: output.append(countdown) tracks: List[Song] = [] num_tracks = len(songs) for index, song in enumerate(songs, start=1): if self.progress.abort: return tracks if index > 1: output.append(transition, overlap=overlap) cur_pos = output.duration next_track = self.mp3_editor.use(song) if self.options.mode == GameMode.QUIZ: try: start, end = Assets.QUIZ_COUNTDOWN_POSITIONS[str(index)] number = countdown.clip(start, end) except KeyError: break output.append(number) output.append(transition) output.append(next_track, overlap=overlap) song_with_pos = song.marshall(exclude=["filename", "ref_id"]) song_with_pos['start_time'] = cur_pos.format() tracks.append( Song(song.filename, song._parent, song.ref_id, **song_with_pos)) self.progress.text = f'Adding track {index}/{num_tracks}' self.progress.pct = 100.0 * float(index) / float(num_tracks) output.append(transition, overlap=overlap) if self.options.crossfade > 0: # if we need to re-encode the stream anyway, might as well also # do loudness normalisation output.normalize(1) self.progress.text = 'Generating MP3 file' self.progress.current_phase = 2 output.generate() if self.progress.abort: return tracks self.progress.text = 'MP3 Generated, creating track listing PDF' self.generate_track_listing(tracks) self.progress.text = 'MP3 and Track listing PDF generated' self.progress.pct = 100.0 return tracks