1#!/usr/bin/python 2 3# Audio Tools, a module and set of tools for manipulating audio data 4# Copyright (C) 2007-2014 Brian Langenberger 5 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, write to the Free Software 18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 20 21import sys 22import os 23import os.path 24import select 25from operator import concat 26import audiotools 27import audiotools.ui 28import audiotools.text as _ 29import termios 30 31 32MAX_CPUS = audiotools.MAX_JOBS 33 34 35def convert(progress, 36 source_audiofile, 37 destination_filename, 38 destination_class, 39 compression, 40 metadata, 41 replay_gain, 42 sample_rate, 43 channels, 44 bits_per_sample): 45 try: 46 if (((sample_rate is None) and 47 (channels is None) and 48 (bits_per_sample is None))): 49 destination_audiofile = source_audiofile.convert( 50 destination_filename, 51 destination_class, 52 compression, 53 progress) 54 else: 55 pcmreader = source_audiofile.to_pcm() 56 destination_audiofile = destination_class.from_pcm( 57 destination_filename, 58 audiotools.PCMConverter( 59 audiotools.PCMReaderProgress( 60 pcmreader, 61 source_audiofile.total_frames(), 62 progress), 63 sample_rate if 64 (sample_rate is not None) else 65 pcmreader.sample_rate, 66 channels if 67 (channels is not None) else 68 pcmreader.channels, 69 0 if 70 (channels is not None) else 71 pcmreader.channel_mask, 72 bits_per_sample if (bits_per_sample is not None) else 73 pcmreader.bits_per_sample), 74 compression, 75 source_audiofile.total_frames() if 76 (source_audiofile.lossless() and (sample_rate is None)) 77 else None) 78 79 if metadata is not None: 80 destination_audiofile.set_metadata(metadata) 81 82 if replay_gain is not None: 83 destination_audiofile.set_replay_gain(replay_gain) 84 85 existing_cuesheet = source_audiofile.get_cuesheet() 86 if existing_cuesheet is not None: 87 destination_audiofile.set_cuesheet(existing_cuesheet) 88 except KeyboardInterrupt: 89 # delete partially-encoded file 90 try: 91 os.unlink(destination_filename) 92 except OSError: 93 pass 94 95 return destination_filename 96 97 98def __add_replay_gain__(tracks, progress=None): 99 """a wrapper around add_replay_gain that catches KeyboardInterrupt""" 100 101 try: 102 audiotools.add_replay_gain(tracks=tracks, progress=progress) 103 except KeyboardInterrupt: 104 pass 105 106 107if audiotools.ui.AVAILABLE: 108 urwid = audiotools.ui.urwid 109 110 class MultiOutputFiller(audiotools.ui.OutputFiller): 111 def __init__(self, 112 track_labels, 113 metadata_choices, 114 input_filenames, 115 output_directory, 116 format_string, 117 output_class, 118 quality, 119 completion_label=u"Apply"): 120 """track_labels[a][t] 121 is a unicode string per album "a", per track "t" 122 123 metadata_choices[a][c][t] 124 is a MetaData object per album "a", per choice "c", per track "t" 125 must have same album count as track_labels 126 and each album's choice must have the same number of tracks 127 (but may have different number of choices) 128 129 input_filenames[a][t] 130 is a Filename object per album "a", per track "t" 131 132 output_directory is a string of the default output dir 133 134 format_string is a UTF-8 encoded format string 135 136 output_class is the default AudioFile-compatible class 137 138 quality is a string of the default output quality to use 139 """ 140 141 from functools import reduce 142 143 self.__cancelled__ = True 144 145 # ensure there's at least one album 146 assert(len(track_labels) > 0) 147 148 # ensure album count is consistent 149 assert(len(track_labels) == 150 len(metadata_choices) == 151 len(input_filenames)) 152 153 # ensure track count is consistent 154 for (labels, filenames) in zip(track_labels, input_filenames): 155 assert(len(labels) == len(filenames)) 156 157 # ensure there's at least one track 158 assert(len(track_labels[0]) > 0) 159 160 # ensure there's at least one set of choices per album 161 # and all have the same number of tracks 162 for (a, choice_labels) in zip(metadata_choices, track_labels): 163 assert(len(a) > 0) 164 for c in a: 165 assert(len(c) == len(choice_labels)) 166 167 # ensure all input filenames are Filename objects 168 for a in input_filenames: 169 for f in a: 170 assert(isinstance(f, audiotools.Filename)) 171 172 # setup status bars for output messages 173 self.metadatas_status = [urwid.Text(u"") for m in track_labels] 174 self.options_status = urwid.Text(u"") 175 176 # setup widgets for populated metadata fields 177 self.metadatas = [ 178 audiotools.ui.MetaDataFiller(labels, choices, status) 179 for (labels, choices, status) in zip(track_labels, 180 metadata_choices, 181 self.metadatas_status)] 182 183 # setup a widget for populating output parameters 184 self.options = audiotools.ui.OutputOptions( 185 output_dir=output_directory, 186 format_string=format_string, 187 audio_class=output_class, 188 quality=quality, 189 input_filenames=reduce(concat, input_filenames), 190 metadatas=[None for f in reduce(concat, input_filenames)]) 191 192 # finish initialization 193 from audiotools.text import LAB_CANCEL_BUTTON 194 195 self.wizard = audiotools.ui.Wizard( 196 self.metadatas + [self.options], 197 urwid.Button(LAB_CANCEL_BUTTON, on_press=self.exit), 198 urwid.Button(completion_label, on_press=self.complete), 199 self.page_changed) 200 201 self.__current_page__ = self.metadatas[0] 202 203 urwid.Frame.__init__(self, 204 body=self.wizard, 205 footer=self.metadatas_status[0]) 206 207 def page_changed(self, new_page): 208 self.__current_page__ = new_page 209 if hasattr(new_page, "status"): 210 # one of the metadata pages is selected 211 self.set_footer(new_page.status) 212 else: 213 from functools import reduce 214 215 # the final options page is selected 216 self.options.set_metadatas( 217 reduce(concat, [list(m.populated_metadata()) 218 for m in self.metadatas])) 219 self.set_footer(self.options_status) 220 221 def handle_text(self, i): 222 if ((i == "f1") and hasattr(self.__current_page__, 223 "select_previous_item")): 224 self.__current_page__.select_previous_item() 225 elif ((i == "f2") and (hasattr(self.__current_page__, 226 "select_next_item"))): 227 self.__current_page__.select_next_item() 228 229 def output_albums(self): 230 """for each album 231 yields (output_class, 232 output_filename, 233 output_quality, 234 output_metadata) tuple for each input audio file 235 236 as a nested iterator 237 238 output_metadata is a newly created MetaData object""" 239 240 (audiofile_class, 241 quality, 242 output_filenames) = self.options.selected_options() 243 244 for widget in self.metadatas: 245 album = [] 246 for metadata in widget.populated_metadata(): 247 album.append((audiofile_class, 248 output_filenames.pop(0), 249 quality, 250 metadata)) 251 yield album 252 253 254if (__name__ == '__main__'): 255 import argparse 256 257 parser = argparse.ArgumentParser(description=_.DESCRIPTION_TRACK2TRACK) 258 259 parser.add_argument("--version", 260 action="version", 261 version="Python Audio Tools %s" % (audiotools.VERSION)) 262 263 parser.add_argument("-I", "--interactive", 264 action="store_true", 265 default=False, 266 dest="interactive", 267 help=_.OPT_INTERACTIVE_OPTIONS) 268 269 parser.add_argument("-V", "--verbose", 270 dest="verbosity", 271 choices=audiotools.VERBOSITY_LEVELS, 272 default=audiotools.DEFAULT_VERBOSITY, 273 help=_.OPT_VERBOSE) 274 275 conversion = parser.add_argument_group(_.OPT_CAT_CONVERSION) 276 277 conversion.add_argument("-t", "--type", 278 dest="type", 279 choices=sorted(list(audiotools.TYPE_MAP.keys()) + 280 ["help"]), 281 help=_.OPT_TYPE) 282 283 conversion.add_argument("-q", "--quality", 284 dest="quality", 285 help=_.OPT_QUALITY) 286 287 conversion.add_argument("-d", "--dir", 288 dest="dir", 289 default=".", 290 help=_.OPT_DIR) 291 292 conversion.add_argument("--format", 293 default=None, 294 dest="format", 295 help=_.OPT_FORMAT) 296 297 conversion.add_argument("-o", "--output", 298 dest="output", 299 help=_.OPT_OUTPUT_TRACK2TRACK) 300 301 conversion.add_argument("-j", "--joint", 302 type=int, 303 default=MAX_CPUS, 304 dest="max_processes", 305 help=_.OPT_JOINT) 306 307 format = parser.add_argument_group(_.OPT_CAT_OUTPUT_FORMAT) 308 309 format.add_argument("--sample-rate", 310 type=int, 311 dest="sample_rate", 312 metavar="RATE", 313 help=_.OPT_SAMPLE_RATE) 314 315 format.add_argument("--channels", 316 type=int, 317 dest="channels", 318 help=_.OPT_CHANNELS) 319 320 format.add_argument("--bits-per-sample", 321 type=int, 322 dest="bits_per_sample", 323 metavar="BITS", 324 help=_.OPT_BPS) 325 326 lookup = parser.add_argument_group(_.OPT_CAT_CD_LOOKUP) 327 328 lookup.add_argument("-M", "--metadata-lookup", 329 action="store_true", 330 default=False, 331 dest="metadata_lookup", 332 help=_.OPT_METADATA_LOOKUP) 333 334 lookup.add_argument("--musicbrainz-server", 335 dest="musicbrainz_server", 336 default=audiotools.MUSICBRAINZ_SERVER, 337 metavar="HOSTNAME") 338 339 lookup.add_argument("--musicbrainz-port", 340 type=int, 341 dest="musicbrainz_port", 342 default=audiotools.MUSICBRAINZ_PORT, 343 metavar="PORT") 344 345 lookup.add_argument("--no-musicbrainz", 346 action="store_false", 347 dest="use_musicbrainz", 348 default=audiotools.MUSICBRAINZ_SERVICE, 349 help=_.OPT_NO_MUSICBRAINZ) 350 351 lookup.add_argument("--freedb-server", 352 dest="freedb_server", 353 default=audiotools.FREEDB_SERVER, 354 metavar="HOSTNAME") 355 356 lookup.add_argument("--freedb-port", 357 type=int, 358 dest="freedb_port", 359 default=audiotools.FREEDB_PORT, 360 metavar="PORT") 361 362 lookup.add_argument("--no-freedb", 363 action="store_false", 364 dest="use_freedb", 365 default=audiotools.FREEDB_SERVICE, 366 help=_.OPT_NO_FREEDB) 367 368 lookup.add_argument("-D", "--default", 369 dest="use_default", 370 action="store_true", 371 default=False, 372 help=_.OPT_DEFAULT) 373 374 metadata = parser.add_argument_group(_.OPT_CAT_METADATA) 375 376 metadata.add_argument("--replay-gain", 377 action="store_true", 378 default=None, 379 dest="add_replay_gain", 380 help=_.OPT_REPLAY_GAIN) 381 382 metadata.add_argument("--no-replay-gain", 383 action="store_false", 384 default=None, 385 dest="add_replay_gain", 386 help=_.OPT_NO_REPLAY_GAIN) 387 388 parser.add_argument("filenames", 389 metavar="FILENAME", 390 nargs="+", 391 help=_.OPT_INPUT_FILENAME) 392 393 options = parser.parse_args() 394 395 msg = audiotools.Messenger(options.verbosity == "quiet") 396 397 # ensure interactive mode is available, if selected 398 if options.interactive and (not audiotools.ui.AVAILABLE): 399 audiotools.ui.not_available_message(msg) 400 sys.exit(1) 401 402 # if one specifies incompatible output options, 403 # complain about it right away 404 if options.output is not None: 405 if options.dir != ".": 406 msg.error(_.ERR_TRACK2TRACK_O_AND_D) 407 msg.info(_.ERR_TRACK2TRACK_O_AND_D_SUGGESTION) 408 sys.exit(1) 409 410 if options.format is not None: 411 msg.warning(_.ERR_TRACK2TRACK_O_AND_FORMAT) 412 413 # get the AudioFile class we are converted to 414 if options.type == 'help': 415 audiotools.ui.show_available_formats(msg) 416 sys.exit(0) 417 elif options.output is None: 418 if options.type is not None: 419 AudioType = audiotools.TYPE_MAP[options.type] 420 else: 421 AudioType = audiotools.TYPE_MAP[audiotools.DEFAULT_TYPE] 422 else: 423 if options.type is not None: 424 AudioType = audiotools.TYPE_MAP[options.type] 425 else: 426 try: 427 AudioType = audiotools.filename_to_type(options.output) 428 except audiotools.UnknownAudioType as exp: 429 exp.error_msg(msg) 430 sys.exit(1) 431 432 # whether to add ReplayGain to newly converted files 433 if AudioType.supports_replay_gain(): 434 add_replay_gain = ((options.add_replay_gain if 435 (options.add_replay_gain is not None) else 436 audiotools.ADD_REPLAYGAIN)) 437 else: 438 add_replay_gain = False 439 440 # ensure the selected compression is compatible with that class 441 if options.quality == 'help': 442 audiotools.ui.show_available_qualities(msg, AudioType) 443 sys.exit(0) 444 elif options.quality is None: 445 options.quality = audiotools.__default_quality__(AudioType.NAME) 446 elif options.quality not in AudioType.COMPRESSION_MODES: 447 msg.error(_.ERR_UNSUPPORTED_COMPRESSION_MODE % 448 {"quality": options.quality, 449 "type": AudioType.NAME}) 450 sys.exit(1) 451 452 # grab the list of AudioFile objects we are converting from 453 input_filenames = set() 454 try: 455 audiofiles = audiotools.open_files(options.filenames, 456 messenger=msg, 457 no_duplicates=True, 458 opened_files=input_filenames) 459 except audiotools.DuplicateFile as err: 460 msg.error(_.ERR_DUPLICATE_FILE % (err.filename,)) 461 sys.exit(1) 462 463 if len(audiofiles) < 1: 464 msg.error(_.ERR_FILES_REQUIRED) 465 sys.exit(1) 466 467 if (options.sample_rate is not None) and (options.sample_rate < 1): 468 msg.error(_.ERR_INVALID_SAMPLE_RATE) 469 sys.exit(1) 470 471 if (options.channels is not None) and (options.channels < 1): 472 msg.error(_.ERR_INVALID_CHANNEL_COUNT) 473 sys.exit(1) 474 475 if (((options.bits_per_sample is not None) and 476 (options.bits_per_sample < 1))): 477 msg.error(_.ERR_INVALID_BITS_PER_SAMPLE) 478 sys.exit(1) 479 480 if options.max_processes < 1: 481 msg.error(_.ERR_INVALID_JOINT) 482 sys.exit(1) 483 484 if (options.output is not None) and (len(audiofiles) != 1): 485 msg.error(_.ERR_TRACK2TRACK_O_AND_MULTIPLE) 486 sys.exit(1) 487 488 if options.output is None: 489 # the default encoding method, without an output file 490 491 queue = audiotools.ExecProgressQueue(msg) 492 493 # split tracks by album 494 albums_iter = audiotools.group_tracks(audiofiles) 495 496 # input_tracks[a][t] is an AudioFile object 497 # per album "a", per track "t" 498 input_tracks = [] 499 500 # input_metadatas[a][t] is a MetaData object (or None) 501 # per album "a", per track "t" 502 input_metadatas = [] 503 504 # track_labels[a][t] is a unicode string per album "a", per track "t" 505 track_labels = [] 506 507 # metadata_choices[a][c][t] is a MetaData object 508 # per album "a", per choice "c", per track "t" 509 metadata_choices = [] 510 511 # input_filenames[a][t] is a Filename object 512 # per album "a", per track "t" 513 input_filenames = [] 514 515 for album_tracks in albums_iter: 516 input_tracks.append(album_tracks) 517 518 track_metadatas = [f.get_metadata() for f in album_tracks] 519 input_metadatas.append(track_metadatas) 520 521 filenames = [audiotools.Filename(f.filename) 522 for f in album_tracks] 523 track_labels.append([f.basename().__unicode__() 524 for f in filenames]) 525 input_filenames.append(filenames) 526 527 if not options.metadata_lookup: 528 # pull metadata from existing files, if any 529 metadata_choices.append([[f.get_metadata() for f in 530 album_tracks]]) 531 else: 532 # perform CD lookup for existing files 533 try: 534 metadata_choices.append(audiotools.track_metadata_lookup( 535 audiofiles=album_tracks, 536 musicbrainz_server=options.musicbrainz_server, 537 musicbrainz_port=options.musicbrainz_port, 538 freedb_server=options.freedb_server, 539 freedb_port=options.freedb_port, 540 use_musicbrainz=options.use_musicbrainz, 541 use_freedb=options.use_freedb)) 542 except KeyboardInterrupt: 543 msg.ansi_clearline() 544 msg.error(_.ERR_CANCELLED) 545 sys.exit(1) 546 547 # and prepend metadata from existing files as an option, if any 548 if track_metadatas != [None] * len(track_metadatas): 549 metadata_choices[-1].insert( 550 0, 551 [(m if m is not None else audiotools.MetaData()) 552 for m in track_metadatas]) 553 554 # avoid performing too many lookups in a row too quickly 555 from time import sleep 556 sleep(1) 557 558 # a list of (audiofile, 559 # output_class, 560 # output_filename, 561 # output_quality, 562 # output_metadata, 563 # output_replay_gain) tuples to be executed 564 conversion_jobs = [] 565 566 # a list of [audiofile, audiofile, ...] lists 567 # each containing an album of tracks to add ReplayGain to 568 # once conversion is finished 569 replaygain_jobs = [] 570 571 if options.interactive: 572 # pick options using interactive widget 573 574 output_widget = MultiOutputFiller( 575 track_labels=track_labels, 576 metadata_choices=metadata_choices, 577 input_filenames=input_filenames, 578 output_directory=options.dir, 579 format_string=(options.format if 580 (options.format is not None) else 581 audiotools.FILENAME_FORMAT), 582 output_class=AudioType, 583 quality=options.quality, 584 completion_label=(_.LAB_TRACK2TRACK_APPLY if 585 (sum(map(len, input_tracks)) != 1) 586 else _.LAB_TRACK2TRACK_APPLY_1)) 587 588 loop = audiotools.ui.urwid.MainLoop( 589 output_widget, 590 audiotools.ui.style(), 591 unhandled_input=output_widget.handle_text, 592 pop_ups=True) 593 try: 594 loop.run() 595 msg.ansi_clearscreen() 596 except (termios.error, IOError): 597 msg.error(_.ERR_TERMIOS_ERROR) 598 msg.info(_.ERR_TERMIOS_SUGGESTION) 599 msg.info(audiotools.ui.xargs_suggestion(sys.argv)) 600 sys.exit(1) 601 602 if output_widget.cancelled(): 603 sys.exit(0) 604 605 for (album_tracks, 606 album_metadatas, 607 album_output) in zip(input_tracks, 608 input_metadatas, 609 output_widget.output_albums()): 610 611 # use existing ReplayGain values if we're to add it 612 # and *all* input tracks have it 613 if add_replay_gain: 614 album_track_gains = [t.get_replay_gain() for t in 615 album_tracks] 616 if None in album_track_gains: 617 album_track_gains = [None for t in album_tracks] 618 replaygain_jobs.append( 619 [str(o[1]) for o in album_output]) 620 else: 621 album_track_gains = [None for t in album_tracks] 622 623 for (input_track, 624 current_metadata, 625 (output_class, 626 output_filename, 627 output_quality, 628 output_metadata), 629 output_replay_gain) in zip(album_tracks, 630 album_metadatas, 631 album_output, 632 album_track_gains): 633 # merge current track metadata (if any) 634 # with metadata returned from widget 635 if current_metadata is not None: 636 for attr in audiotools.MetaData.FIELDS: 637 original_value = getattr(current_metadata, attr) 638 updated_value = getattr(output_metadata, attr) 639 if original_value != updated_value: 640 setattr(current_metadata, attr, updated_value) 641 # and queue up conversion job to be executed 642 conversion_jobs.append((input_track, 643 output_class, 644 output_filename, 645 output_quality, 646 current_metadata, 647 output_replay_gain)) 648 else: 649 # or simply queue up conversion job to be executed 650 # using only new metadata 651 conversion_jobs.append((input_track, 652 output_class, 653 output_filename, 654 output_quality, 655 output_metadata, 656 output_replay_gain)) 657 658 else: 659 # pick options without using GUI 660 for (album_tracks, 661 album_metadata_choices, 662 album_filenames) in zip(input_tracks, 663 metadata_choices, 664 input_filenames): 665 try: 666 output_tracks = list(audiotools.ui.process_output_options( 667 metadata_choices=album_metadata_choices, 668 input_filenames=album_filenames, 669 output_directory=options.dir, 670 format_string=options.format, 671 output_class=AudioType, 672 quality=options.quality, 673 msg=msg, 674 use_default=options.use_default)) 675 676 # use existing ReplayGain values if we're to add it 677 # and *all* input tracks have it 678 if add_replay_gain: 679 album_track_gains = [t.get_replay_gain() for t in 680 album_tracks] 681 if None in album_track_gains: 682 album_track_gains = [None for t in album_tracks] 683 replaygain_jobs.append( 684 [str(o[1]) for o in output_tracks]) 685 else: 686 album_track_gains = [None for t in album_tracks] 687 688 # queue jobs to be executed 689 for (album_track, 690 (output_class, 691 output_filename, 692 output_quality, 693 output_metadata), 694 output_replay_gain) in zip(album_tracks, 695 output_tracks, 696 album_track_gains): 697 conversion_jobs.append((album_track, 698 output_class, 699 output_filename, 700 output_quality, 701 output_metadata, 702 output_replay_gain)) 703 except audiotools.UnsupportedTracknameField as err: 704 err.error_msg(msg) 705 sys.exit(1) 706 except (audiotools.InvalidFilenameFormat, 707 audiotools.OutputFileIsInput, 708 audiotools.DuplicateOutputFile) as err: 709 msg.error(err) 710 sys.exit(1) 711 712 # re-check that the same output file doesn't occur more than once 713 # (although process_output_options also performs that check, 714 # it's possible processing multiple albums at once 715 # may result in the same output file occurring across the whole 716 # job, but not in any individual album) 717 output_filenames = set() 718 for job in conversion_jobs: 719 output_filename = job[2] 720 if output_filename not in output_filenames: 721 output_filenames.add(output_filename) 722 else: 723 msg.error(_.ERR_DUPLICATE_OUTPUT_FILE % (output_filename,)) 724 sys.exit(1) 725 726 # queue conversion jobs to ProgressQueue 727 for (audiofile, 728 output_class, 729 output_filename, 730 output_quality, 731 output_metadata, 732 output_replay_gain) in conversion_jobs: 733 # try to create subdirectories in advance 734 # so to bail out early if there's an error creating one 735 try: 736 audiotools.make_dirs(str(output_filename)) 737 except OSError: 738 msg.error(_.ERR_ENCODING_ERROR % (output_filename,)) 739 sys.exit(1) 740 741 queue.execute( 742 function=convert, 743 progress_text=output_filename.__unicode__(), 744 completion_output=(_.LAB_ENCODE % { 745 "source": audiotools.Filename(audiofile.filename), 746 "destination": output_filename}), 747 source_audiofile=audiofile, 748 destination_filename=str(output_filename), 749 destination_class=output_class, 750 compression=output_quality, 751 metadata=output_metadata, 752 replay_gain=output_replay_gain, 753 sample_rate=options.sample_rate, 754 channels=options.channels, 755 bits_per_sample=options.bits_per_sample) 756 757 # perform actual track conversion 758 try: 759 output_files = audiotools.open_files( 760 queue.run(options.max_processes)) 761 except audiotools.EncodingError as err: 762 msg.error(err) 763 sys.exit(1) 764 except KeyboardInterrupt: 765 msg.error(_.ERR_CANCELLED) 766 sys.exit(1) 767 768 # add ReplayGain to converted files, if necessary 769 770 # separate encoded files by album_name and album_number 771 for album in [[audiotools.open(f) for f in fs] 772 for fs in replaygain_jobs]: 773 # add ReplayGain to groups of files 774 # belonging to the same album 775 776 album_number = {(m.album_number if m is not None else None) 777 for m in 778 [f.get_metadata() for f in album]}.pop() 779 780 if album_number is None: 781 progress_text = _.RG_ADDING_REPLAYGAIN 782 completion_output = _.RG_REPLAYGAIN_ADDED 783 else: 784 progress_text = _.RG_ADDING_REPLAYGAIN_TO_ALBUM % \ 785 (album_number) 786 completion_output = _.RG_REPLAYGAIN_ADDED_TO_ALBUM % \ 787 (album_number) 788 789 queue.execute(function=__add_replay_gain__, 790 progress_text=progress_text, 791 completion_output=completion_output, 792 tracks=album) 793 794 try: 795 queue.run(options.max_processes) 796 except ValueError as err: 797 msg.error(err) 798 sys.exit(1) 799 except KeyboardInterrupt: 800 msg.error(_.ERR_CANCELLED) 801 sys.exit(1) 802 else: 803 # encoding only a single file 804 audiofile = audiofiles[0] 805 input_filename = audiotools.Filename(audiofile.filename) 806 807 if options.interactive: 808 track_metadata = audiofile.get_metadata() 809 810 output_widget = audiotools.ui.SingleOutputFiller( 811 track_label=input_filename.__unicode__(), 812 metadata_choices=[track_metadata if track_metadata is not None 813 else audiotools.MetaData()], 814 input_filenames=[input_filename], 815 output_file=options.output, 816 output_class=AudioType, 817 quality=options.quality, 818 completion_label=_.LAB_TRACK2TRACK_APPLY_1) 819 loop = audiotools.ui.urwid.MainLoop( 820 output_widget, 821 audiotools.ui.style(), 822 unhandled_input=output_widget.handle_text, 823 pop_ups=True) 824 loop.run() 825 msg.ansi_clearscreen() 826 827 if not output_widget.cancelled(): 828 (destination_class, 829 output_filename, 830 compression, 831 track_metadata) = output_widget.output_track() 832 else: 833 sys.exit(0) 834 else: 835 output_filename = audiotools.Filename(options.output) 836 destination_class = AudioType 837 compression = options.quality 838 track_metadata = audiofile.get_metadata() 839 840 if input_filename == output_filename: 841 msg.error(_.ERR_OUTPUT_IS_INPUT % 842 (output_filename,)) 843 sys.exit(1) 844 845 progress = audiotools.SingleProgressDisplay( 846 messenger=msg, 847 progress_text=output_filename.__unicode__()) 848 try: 849 convert(progress=progress.update, 850 source_audiofile=audiofile, 851 destination_filename=str(output_filename), 852 destination_class=destination_class, 853 compression=compression, 854 metadata=track_metadata, 855 replay_gain=(audiofile.get_replay_gain() if 856 add_replay_gain else None), 857 sample_rate=options.sample_rate, 858 channels=options.channels, 859 bits_per_sample=options.bits_per_sample) 860 progress.clear_rows() 861 862 msg.output(_.LAB_ENCODE % {"source": input_filename, 863 "destination": output_filename}) 864 except audiotools.EncodingError as err: 865 progress.clear_rows() 866 msg.error(err) 867 sys.exit(1) 868 except KeyboardInterrupt: 869 progress.clear_rows() 870 try: 871 os.unlink(str(output_filename)) 872 except OSError: 873 pass 874 msg.error(_.ERR_CANCELLED) 875 sys.exit(1) 876