示例#1
0
 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
示例#2
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)
示例#3
0
 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
示例#4
0
 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
示例#5
0
 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)
示例#6
0
 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
示例#7
0
    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())
示例#8
0
    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())
示例#9
0
 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