burn-0.4.6/0000755000175000017500000000000011314307442012324 5ustar bignosebignoseburn-0.4.6/copyright0000664000175000017500000004310311222766143014267 0ustar bignosebignose GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. burn-0.4.6/burnlib/0000755000175000017500000000000011314307442013761 5ustar bignosebignoseburn-0.4.6/burnlib/configure.py0000664000175000017500000000767211314132111016317 0ustar bignosebignose# -*- coding: utf-8 -*- # burnlib/configure.py # # Copyright © 2009 Ben Finney . # Copyright © 2004–2009 Gaetano Paolone . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. """ Configuration options for ‘burn’. config The configuration object for parsing config files. config_defaults Default configuration options. config_file_paths List of config file paths that will be used, in order. read_from_files(paths) Sets up the config object with options read from config files. """ import sys import os import os.path import gettext import ConfigParser #gettext gettext.bindtextdomain('burn_configure', '/usr/share/locale/it/LC_MESSAGES/') gettext.textdomain('burn_configure') _ = gettext.gettext config_file_paths = [ os.path.join('/', 'etc', 'burn.conf'), os.path.join(os.environ['HOME'], '.burnrc'), ] config_defaults = { 'ask_root': "yes", 'external_decoding': "no", 'pause': "yes", 'wodim': "/usr/bin/wodim", 'cdrdao': "/usr/bin/cdrdao", 'genisoimage': "/usr/bin/genisoimage", 'mp3_decoder': "/usr/bin/mpg321", 'mp3_decoder_option': "-q -w", 'ogg_decoder': "/usr/bin/ogg123", 'ogg_decoder_option': "-q -d wav -f", 'tempdir': "/tmp/", 'image': "burn_image.iso", 'windows_read': "yes", 'mount_dir': "/mnt/", 'device': "/dev/cdrom", 'speed': "4", 'driver': "generic-mmc", 'burnfree': "yes", 'media-check': "no", 'size': "700", } config = ConfigParser.SafeConfigParser(config_defaults) def read_from_files(parser=None, paths=None): """ Read the configuration from files into the config object. The ConfigParser behaviour is to silently ignore those paths that do not exist, allowing a cascade of potential config files to be read in sequence, each one overriding any previous settings. """ if parser is None: parser = config if paths is None: paths = config_file_paths parser.read(paths) def read_from_template(parser, template_path): """ Read the configuration from specified template file. """ template_file = open(template_path) parser.readfp(template_file) def make_config_from_template(template_path): """ Make a config object from specified template file. If an error occurs reading the configuration template, exit with an error message. """ parser = ConfigParser.SafeConfigParser(config_defaults) try: read_from_template(parser, template_path) except EnvironmentError, exc: message = _( "Failed to read template configuration file: %(exc)s\n" ) % vars() sys.exit(message) return parser def write_to_file(parser, path): """ Write configuration to specified path. If an error occurs opening or writing the configuration file, exit with an error message. """ try: config_file = open(path, 'w') parser.write(config_file) except EnvironmentError, exc: message = _( "Failed to write configuration file: %(exc)s\n" ) % vars() sys.exit(message) # Local variables: # mode: python # coding: utf-8 # End: # vim: filetype=python fileencoding=utf-8 : burn-0.4.6/burnlib/interactive_configure.py0000664000175000017500000004725711314132111020717 0ustar bignosebignose# -*- coding: utf-8 -*- # burnlib/interactive_configure.py # # Copyright © 2009 Ben Finney . # Copyright © 2004–2009 Gaetano Paolone . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. """ Interactive configuration for ‘burn’. main() Generates the ‘burn’ configuration file, from defaults and via interactive console dialogue. """ import sys import os import os.path import gettext import optparse import pwd import textwrap import burnlib.version import configure import console import device #gettext gettext.bindtextdomain('burn_configure', '/usr/share/locale/it/LC_MESSAGES/') gettext.textdomain('burn_configure') _ = gettext.gettext class OptionParser(optparse.OptionParser, object): """ Command-line parser for this program. """ default_program_name = "burn-configure" default_usage = "%prog [options]" default_description = _( "Generate configuration for Burn.") default_epilog = _( "This tool reads the default configuration from a template," " queries interactively for settings specific to this system," " then generates the configuration for Burn" " (Burn until recorded, now!) to a new file.") def __init__( self, prog=default_program_name, version=burnlib.version.version, usage=default_usage, description=default_description, epilog=default_epilog, *args, **kwargs): super(OptionParser, self).__init__( prog=prog, version=version, usage=usage, description=description, epilog=epilog, *args, **kwargs) self.add_option( "-t", "--template-file", action='store', dest='template_file_path', default="/usr/share/burn/example/burn.conf", metavar="PATH", help=_( "Read the configuration template from file PATH" " (default '%default').")) self.add_option( "-o", "--output-file", action='store', dest='output_file_path', default="burn.conf.new", metavar="PATH", help=_( "Write the generated configuration to file PATH" " (default '%default').")) def check_values(self, values, args): """ Check the parsed values and arguments. """ max_args = 0 if len(args) > max_args: args_text = " ".join(args) message = _("unexpected arguments: %(args_text)s") % vars() self.error(message) return (values, args) def prog_intro(path): """ Introductive program output. Also checks if superuser. """ print _( 'Burn-configure v.%(version)s' + ' Written by %(author_name)s.') % vars(burnlib.version) print _( 'This tool helps writing configuration file for' ' burn - Burn until recorded, now!') print _( 'This software comes with absolutely no warranty!' ' Use at your own risk!') print _('Burn-configure is free software.') print _('See software updates at .') % vars(burnlib) print print _('This utility will generate: '), path print _('Place this file as ~/.burnrc or /etc/burn.conf .') print print if not pwd.getpwuid(os.geteuid())[0] == "root": print _('You are not superuser (root).') print _( 'You can still go through this configuration process' ' but remember that you should be root (or have permissions' ' on programs and devices) in order to use burn.') if not console.ask_yesno( _('Continue without superuser privilege'), True): sys.exit() def giveme_realpath(path): """ Checks if path exists and return absolute path. """ if os.path.exists(path): if os.path.islink(path): return os.path.realpath(path) def main(): """ Mainline routine for the burn-configure program. """ option_parser = OptionParser() (options, args) = option_parser.parse_args() #Checking if there is a configuration file generated by this program target_conf_file_path = options.output_file_path prog_intro(path=target_conf_file_path) if os.path.exists(target_conf_file_path): print print _( 'Target configuration file (%(target_conf_file_path)s)' ' already exists.') % vars() if console.ask_yesno(_('Remove existing target file'), False): print _( 'Removing last configuration file created with burn-configure' '...') os.remove(target_conf_file_path) else: print _( 'Exiting... Please remove or rename last configuration file: ' '%(target_conf_file_path)s') % vars() # Set up a target config, populated from template. target_config = configure.make_config_from_template( options.template_file_path) configure_sections = target_config.sections() section = '' #beginning confuguration if 'general' in configure_sections: section = 'general' options = target_config.options(section) if 'external_decoding' in options: print current = console.make_boolean_response( target_config.get(section, 'external_decoding')) print _('Keep native audio decoding?') print _( '\tBurn is able to transform compressed audio files' ' (MP3, Ogg Vorbis) in WAV.') print _('\tChoose \'y\' if you want to keep native decoding. ') print _( '\tChoose \'n\' if you want to use external decoders' ' such as mpg321, ogg123, lame, etc.') print _( '\t(You will need further editing of burn configuration file)') response = console.ask_yesno( _("Keep native audio decoding"), current) target_config.set( section, 'external_decoding', console.make_yesno_response(response)) if 'ask_root' in options: print current = console.make_boolean_response( target_config.get(section, 'ask_root')) print _('Prompt user if he is not root?') print _('\tBurn can prompt the user if he is not root.') print _('\tChoose \'y\' if you want to keep burn prompting you. ') print _('\tChoose \'n\' if you don\'t want this question anymore.') print _( '\t(disable this option if your user has' ' permissions to write with cd-writer)') response = console.ask_yesno( _("Prompt user if he is not root"), current) target_config.set( section, 'ask_root', console.make_yesno_response(response)) if 'ISO' in configure_sections: section = 'ISO' options = target_config.options(section) if 'tempdir' in options: print current = target_config.get(section, 'tempdir') print _('Which is your temporary directory?') while True: tmpdr = console.ask_value( _("Temporary directory path"), current) if tmpdr == '': break if os.path.exists(tmpdr) and os.path.isdir(tmpdr): target_config.set(section, 'tempdir', tmpdr) break else: print tmpdr, _('invalid path... skipped.') break if 'image' in options: print current = target_config.get(section, 'image') print _('Temporary ISO name?') while True: image_filename = console.ask_value( _("Temporary ISO image filename"), current) if image_filename == '': break else: target_config.set(section, 'image', image_filename) break if 'windows_read' in options: print current = console.make_boolean_response( target_config.get(section, 'windows_read')) print _('Enable Joliet?') print _( '\tYou should enable this option if you want' ' to use your CDs on a Windows system too.') response = console.ask_yesno( _("Enable Joliet data (for Windows compatibility)"), current) target_config.set( section, 'windows_read', console.make_yesno_response(response)) if 'mount_dir' in options: print current = target_config.get(section, 'mount_dir') print _('Which is your preferred mount point?') print _('\tBurn allows you to see image contents.') print _('\tWhere do you want to mount the image?') while True: tmpdr = console.ask_value( _("Mount point directory path"), current) if tmpdr == '': break if os.path.exists(tmpdr) and os.path.isdir(tmpdr): target_config.set(section, 'mount_dir', tmpdr) break else: print tmpdr, _('invalid path... skipped.') break if 'CD-writer' in configure_sections: section = 'CD-writer' options = target_config.options(section) if 'device' in options: hypothesis_count = 1 current = target_config.get(section, 'device') print _( '\nWhich is your cd-dvd writer device?' '\n\tEnter either the device file (e.g. /dev/hdc) or ' 'the symbolic link to it (e.g. /dev/cdrom or /dev/cdrw).\n' '\tTraditional SCSI descriptions of devicetype:bus/target/lun ' 'specification (e.g. 1,0,0) are accepted too.\n') print _('\n\n\tPress a key to start guessing your device.\n') console.getch() if giveme_realpath('/dev/cdrw'): print _( '\n\t* Hypotesis #' ), hypothesis_count, _( ': '), giveme_realpath('/dev/cdrw') print _( '\t\t("/dev/cdrw", which usually identifies a ' 'cd-writer unit, points to this device.)') hypothesis_count += 1 if giveme_realpath('/dev/dvdrw'): print _( '\t* Hypotesis #' ),hypothesis_count, _( ': '),giveme_realpath('/dev/dvdrw') print _( '\t\t("/dev/dvdrw", which usually identifies a dvd-writer ' 'unit, points to this device.)') hypothesis_count += 1 if giveme_realpath('/dev/cdrom'): print _( '\t* Hypotesis #' ), hypothesis_count, _( ': '),giveme_realpath('/dev/cdrom') print _( '\t\t("/dev/cdrom" points to this device. If your ' 'cd-reader\n\t\t unit is the same of your cd-writer ' 'unit this should be your device)') hypothesis_count += 1 if giveme_realpath('/dev/dvd'): print _( '\t* Hypotesis #' ), hypothesis_count, _( ': '),giveme_realpath('/dev/dvd') print _( '\t\t("/dev/dvd" points to this device. If your dvd-reader' '\n\t\t unit is the same of your cd-writer ' 'unit this should be your device)') hypothesis_count += 1 print '\n\tPress a key to see wodim\'s device list output.\n' console.getch() print device.device_list_output() if console.ask_yesno(_( '\tDo you also want to see bus/target/lun scsi ' 'specifications'), False): print device.bus_list_output() while True: print _('\n') tmpdr = console.ask_value( _("Optical media recording device"), current) if tmpdr == '': break else: target_config.set(section, 'device', tmpdr) break if 'speed' in options: print current = target_config.get(section, 'speed') print _('At which speed do you want to burn?') print _('\tRemember: higher speed may lead to buffer underrun.') while True: spd = console.ask_value( _("Recording speed"), current) if spd == '': break else: target_config.set(section, 'speed', spd) break if 'driver' in options: print current = target_config.get(section, 'driver') print _('Does your CD-writer use a specific driver?') print _('\tPossible values are: ') values = [ "tcdd2600", "plextor", "plextor-scan", "generic-mmc", "generic-mmc-raw", "ricoh-mp6200", "yamaha-cdr10x", "teac-cdr55", "sony-cdu920", "sony-cdu948", "taiyo-yuden", "toshiba", ] print textwrap.fill( ", ".join(values), initial_indent="\t\t", subsequent_indent="\t\t", break_long_words=False) while True: drvr = console.ask_value( _("Driver value for media writer device"), current) if drvr == '': break else: target_config.set(section, 'driver', drvr) break if 'burnfree' in options: print current = console.make_boolean_response( target_config.get(section, 'burnfree')) print _( 'Do you want to turn the support for' ' Buffer Underrun Free writing on?') print _( '\tThis only works for drives that support' ' Buffer Underrun Free technology') response = console.ask_yesno( _("Enable support for Buffer Underrun Free"), current) target_config.set( section, 'burnfree', console.make_yesno_response(response)) if 'CD-reader' in configure_sections: section = 'CD-reader' print if console.ask_yesno( _('Do you have a second unit as a CD-reader'), False): options = target_config.options(section) hypothesis_count = 1 current = target_config.get(section, 'device') print _( '\nWhich is your cd-dvd reader device?' '\n\tEnter either the device file (e.g. /dev/hdc) or the ' 'symbolic link to it (e.g. /dev/cdrom or /dev/cdrw).\n' '\tTraditional SCSI descriptions of ' 'devicetype:bus/target/lun specification (e.g. 1,0,0) ' 'are accepted too.\n' '\n\n\tPress a key to start guessing your device.\n') console.getch() if giveme_realpath('/dev/cdrom'): print _('\t* Hypotesis #'), hypothesis_count, _( ': '), giveme_realpath('/dev/cdrom') print _( '\t\t("/dev/cdrom" points to this device.') hypothesis_count += 1 if giveme_realpath('/dev/dvd'): print _('\t* Hypotesis #'), hypothesis_count, _( ': '), giveme_realpath('/dev/dvd') print _( '\t\t("/dev/dvd" points to this device.') hypothesis_count += 1 print '\n\tPress a key to see wodim\'s device list output.\n' console.getch() print device.device_list_output() if console.ask_yesno(_( '\tDo you also want to see bus/target/lun ' 'scsi specifications'), False): print device.bus_list_output() while True: tmpdr = console.ask_value( _("Optical media reading device"), current) if tmpdr == '': break else: target_config.set(section, 'device', tmpdr) break if 'driver' in options: print current = target_config.get(section, 'driver') print _('Does your CD-reader use a specific driver?') print _('\tPossible values are: ') values = [ "tcdd2600", "plextor", "plextor-scan", "generic-mmc", "generic-mmc-raw", "ricoh-mp6200", "yamaha-cdr10x", "teac-cdr55", "sony-cdu920", "sony-cdu948", "taiyo-yuden", "toshiba", ] print textwrap.fill( ", ".join(values), initial_indent="\t\t", subsequent_indent="\t\t", break_long_words=False) while True: drvr = console.ask_value( _("Driver value for media writer device"), current) if drvr == '': break else: # target_config.set(section, 'driver', drvr) break if 'Media' in configure_sections: section = 'Media' options = target_config.options(section) if 'size' in options: print current = target_config.get(section, 'size') print _('Which is the most common capacity of your target CDs?') while True: tmpdr = console.ask_value( _("Media capacity (MB)"), current) if tmpdr == '': break else: target_config.set(section, 'size', tmpdr) break if 'media-check' in options: print current = console.make_boolean_response( target_config.get(section, 'media-check')) print _('Do you want burn to auto-check target CD capacity?') print _('\tThis function uses cdrdao.') response = console.ask_yesno( _("Check target media capacity"), current) target_config.set( section, 'media-check', console.make_yesno_response(response)) configure.write_to_file(target_config, target_conf_file_path) print print print _('Congratulations!') print _('Now you have your new configuration file:') print target_conf_file_path print _('Please rename it and place it as ~/.burnrc or /etc/burn.conf') if __name__ == '__main__': exit_status = main() sys.exit(exit_status) # Local variables: # mode: python # coding: utf-8 # End: # vim: filetype=python fileencoding=utf-8 : burn-0.4.6/burnlib/console.py0000644000175000017500000001620311237733167016012 0ustar bignosebignose# -*- coding: utf-8 -*- # burnlib/console.py # # Copyright © 2009 Ben Finney . # Copyright © 2003–2009 Gaetano Paolone . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. """ Console interaction functions for ‘burnlib’. ask_yesno(prompt, default, complaint) Prompts on the console for a yes-or-no response; on bad input, displays a complaint and asks again repeatedly. getch() Get a single character directly from the standard input terminal. """ import sys import tty import termios def ask_value(prompt, default=None): """ Prompt for an input value from the console. The specified `prompt` string is used to prompt for the response. If a default is specified, an empty response will instead return the default; otherwise, the response as input is returned. """ prompt_template = "%(prompt)s? " if default is not None: prompt_template = "%(prompt)s [%(default)s]? " response = raw_input(prompt_template % vars()) if not response: if default is not None: response = default return response def make_boolean_response(in_text): """ Make a boolean value from the specified response. If the `in_text` is a case-insensitive substring match of one of the valid words 'no' or 'yes', the result is ``False`` or ``True`` respectively. If the `in_text` is empty or invalid, a `ValueError` is raised. """ result = None response_map = { "no": False, "yes": True, } if in_text: for word, value in response_map.items(): if word.find(in_text.lower()) == 0: result = value break if result is None: raise ValueError() return result def make_yesno_response(in_value): """ Make a "yes"/"no" response string from the specified boolean value. """ result = None response_map = { False: "no", True: "yes", } result = response_map[in_value] return result def get_yesno_response(prompt, default=None, complaint=None): """ Prompt for a yes-or-no response from the console. The specified `prompt` string is used to prompt for the response. The response is converted to a bool value (with an optional default value of `default`) and returned, or `ValueError` raised if the conversion fails. """ default_response = None if default is not None: default_response = make_yesno_response(default) response = ask_value("%(prompt)s (yes/no)" % vars(), default_response) try: result = make_boolean_response(response) except ValueError: if complaint: sys.stderr.write("%(complaint)s\n" % vars()) raise return result def ask_yesno(prompt, default=None, complaint="Please answer [y]es or [n]o."): """ Ask for a response from the console until yes-or-no response. """ result = None while result not in (True, False): try: result = get_yesno_response(prompt, default, complaint) except ValueError: pass return result def getch(): """ Read a single character from the standard input terminal. Read and return exactly one character from the stdin terminal in raw mode, restoring terminal attributes afterward. From recipe by Danny Yoo. """ stdin_fd = sys.stdin.fileno() old_term_attributes = termios.tcgetattr(stdin_fd) try: tty.setraw(stdin_fd) char = sys.stdin.read(1) finally: termios.tcsetattr(stdin_fd, termios.TCSADRAIN, old_term_attributes) return char class ProgressBar(object): """ Progress bar for displaying feedback on progress to console. """ def __init__(self, width, max_amount=100): """ Set up a new instance. """ self.width = width self.min_amount = 0 self.max_amount = max_amount self._amount = 0 def _get_amount(self): """ Getter function for ‘amount’ property. """ return self._amount def _set_amount(self, value): """ Setter function for ‘amount’ property. """ self._amount = value self._amount = min(self._amount, self.max_amount) self._amount = max(self._amount, self.min_amount) amount = property( fget=_get_amount, fset=_set_amount, fdel=None, doc="The current numeric amount of the progress bar.") def _amount_ratio(self): """ Get the ratio of the amount to the progress span. """ span = (self.max_amount - self.min_amount) amount_ratio = float(self.amount) / span return amount_ratio def _progress_width(self): """ Get the width of the progress area of the bar. """ progress_width = self.width - len("[]") return progress_width def _amount_width(self): """ Get the width of the current bar amount. """ progress_width = self._progress_width() amount_ratio = self._amount_ratio() amount_width = int(progress_width * amount_ratio) return amount_width def _decorate_bar_with_percentage(self, bar_text): """ Decorate the bar text with a percentage of the amount. """ amount_ratio = self._amount_ratio() percent_done = int(round(amount_ratio * 100)) percent_text = "%(percent_done)d%%" % vars() percent_begin_offset = int((len(bar_text) - len(percent_text)) / 2) percent_end_offset = percent_begin_offset + len(percent_text) bar_text = ( bar_text[:percent_begin_offset] + percent_text + bar_text[percent_end_offset:]) return bar_text def __str__(self): """ Return the string representation of the progress bar. """ progress_width = self._progress_width() amount_width = self._amount_width() amount_ticks = "=" * amount_width empty_ticks = " " * (progress_width - amount_width) bar_text = "[%(amount_ticks)s%(empty_ticks)s]" % vars() bar_text = self._decorate_bar_with_percentage(bar_text) return bar_text def update_display(self): """ Update display of progress bar to stderr. """ output_text = str(self) + "\r" sys.stderr.write(output_text) sys.stderr.flush() # Local variables: # mode: python # coding: utf-8 # End: # vim: filetype=python fileencoding=utf-8 : burn-0.4.6/burnlib/version.py0000664000175000017500000000207611314307242016025 0ustar bignosebignose# -*- coding: utf-8 -*- # burnlib/version.py # Part of Burn, an optical media recording toolkit. # # Copyright © 2009 Ben Finney . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. """ Version information for ‘burnlib’ package. """ version = "0.4.6" author_name = "Gaetano Paolone" author_email = "bigpaul@hacknight.org" author = "%(author_name)s <%(author_email)s>" license = "GPL-2+" burn-0.4.6/burnlib/audio.py0000664000175000017500000001251311314132111015425 0ustar bignosebignose# -*- coding: utf-8 -*- # burnlib/audio.py # # Copyright © 2009 Ben Finney . # Copyright © 2003–2009 Gaetano Paolone . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. """ Functionality for working with audio files. """ import os.path import ogg.vorbis import eyeD3 import mad class FileInfo: """ Grab as much info as you can from the file given. Reads time, tags, title, artist, and more from file. """ def __init__(self, fullpath): """ Set up the file information from the specified path. """ base, ext = os.path.splitext(fullpath) if ext.lower() == '.ogg': fileinfo = OggInfo(fullpath) self.__dict__.update(fileinfo.__dict__) self.fileinfo = fileinfo else: tag = eyeD3.Tag() tag.link(fullpath) self.artist = tag.getArtist() self.title = tag.getTitle() self.album = tag.getAlbum() fileinfo = mad.MadFile(fullpath) self.total_time = fileinfo.total_time() / 1024 self.bitrate = fileinfo.bitrate() / 1000 self.fileinfo = fileinfo self.duration = compute_duration(self.total_time) def read(self): """ Read and return the file information. """ return self.fileinfo.read() class OggInfo: """Extra information about an Ogg Vorbis file. Uses ogg-python and vorbis-python from http://www.duke.edu/~ahc4/pyogg/. Patch from Justin Erenkrantz """ def __init__(self, name): # Setup the defaults self.valid = 0 self.total_time = 0 self.samplerate = 'unkown' self.bitrate = 'unkown' self.mode = '' self.mode_extension = '' self.title = '' self.artist = '' self.album = '' self.year = '' self.genre = '' self.vendor = '' self.track = '' self.comment = '' self.transcoded = '' # Generic File Info fileogg = ogg.vorbis.VorbisFile(name) ogg_comment = fileogg.comment() ogg_info = fileogg.info() self.fileogg = fileogg # According to the docs, -1 means the current bitstream self.samplerate = ogg_info.rate self.total_time = fileogg.time_total(-1) self.bitrate = fileogg.bitrate(-1) / 1000 self.filesize = fileogg.raw_total(-1)/1024/1024 # recognized_comments = ('Artist', 'Album', 'Title', 'Version', # 'Organization', 'Genre', 'Description', # 'Date', 'Location', 'Copyright', 'Vendor') for key, val in ogg_comment.items(): if key == 'TITLE': self.title = val elif key == 'ARTIST': self.artist = val elif key == 'ALBUM': self.album = val elif key == 'DATE': self.year = val elif key == 'GENRE': self.genre = val elif key == 'VENDOR': self.vendor = val elif key == 'TRACKNUMBER': self.track = val elif key == 'COMMENT': self.comment = val elif key == 'TRANSCODED': self.transcoded = val self.valid = 1 def read(self): """ Read and return the file information. """ return self.fileogg.read() def file_duration(audio_file): """ Return audio duration in seconds from specified audio file. """ seconds = 0 if os.path.exists(audio_file) and os.path.isfile(audio_file): info = FileInfo(audio_file) seconds = seconds + int(info.total_time) return seconds else: return -1 def compute_duration(seconds): """ Return duration string for duration in seconds. Will return the appropriate format for the duration: * "H:MM:SS" for durations one hour or more * "M:SS" for durations one minute or more * "0:SS" for durations less than one minute """ seconds_per_hour = 3600 minutes_per_hour = 60 seconds_per_minute = 60 if seconds >= seconds_per_hour: duration = '%d:%02d:%02d' % ( int(seconds / seconds_per_hour), int(seconds / seconds_per_minute) % minutes_per_hour, int(seconds) % seconds_per_minute) elif seconds >= seconds_per_minute: duration = '%d:%02d' % ( int(seconds / seconds_per_minute), int(seconds) % seconds_per_minute) else: duration ='0:%02d' % int(seconds) return duration # Local variables: # mode: python # coding: utf-8 # End: # vim: filetype=python fileencoding=utf-8 : burn-0.4.6/burnlib/burn.py0000664000175000017500000012743011314132111015277 0ustar bignosebignose# -*- coding: utf-8 -*- # burnlib/burn.py # # Copyright © 2009 Ben Finney . # Copyright © 2003–2009 Gaetano Paolone . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. import os import os.path import sys import optparse import re import statvfs import mad import ao import ogg.vorbis import gettext import pwd import fnmatch import shlex import subprocess import burnlib.version import console import configure from configure import config import audio gettext.bindtextdomain('burn', '/usr/share/locale/it/LC_MESSAGES/') gettext.textdomain('burn') _ = gettext.gettext class ContentMode(object): """ Modes for content handling. """ (data, iso, copy, audio) = (object() for n in range(4)) titles = { audio: _('Audio-CD'), data: _('Data-CD'), copy: _('Copy-CD'), iso: _('Iso-CD'), } def du(dirname, path_du, follow_symlink=False): #is like du. goes through dirname #and subdirs and append every file size to path_du """Goes deeply through path in order to calculate path's disk usage.""" for root, dirs, files in os.walk(dirname): for file_name in files: if os.path.exists(os.path.realpath(os.path.join(root, file_name))): path_du += os.path.getsize(os.path.join(root, file_name)) if follow_symlink: for directory in dirs: print os.path.join(root, directory) #os.walk does not follow symlink directories. #Next line will allow it. if os.path.islink(os.path.join(root, directory)): path_du += du( os.path.realpath(os.path.join(root, directory)), path_du, follow_symlink) return path_du def check_main_option(opt): """Checks first argument.""" if opt not in [ "-D", "--data-cd", "-I", "--iso-cd", "-C", "--copy-cd", "-A", "--audio-cd", ]: print _( 'Invalid syntax. First argument should be a main mode.' ' See \'burn -h\' for more info.') sys.exit() def check_media_empty(): """ Return True if media device is empty. """ command_args = [ config.get('executables', 'cdrdao'), "disk-info", "--device", config.get('CD-writer', 'device'), "--driver", config.get('CD-writer', 'driver'), ] command_process = subprocess.Popen( command_args, close_fds=True, stdout=subprocess.PIPE, stderr=open(os.devnull)) for line in command_process.stdout: if line.startswith('CD-R empty'): if line.split(':')[1].strip() == 'yes': return True return False return False def err(why): """Prints an error using why as argument.""" print _('Error. '), why def err_nf(dir_file): """Not found error message.""" print _('Error. '), dir_file, _(' not found.') def get_list_from_file(path): """extract a list of paths from a file""" in_file = open(path) in_file_lines = [ path for path in ( line.strip() for line in in_file) if os.path.isfile(path)] return in_file_lines def get_media_capacity(): """ Get total capacity of media device. """ command_args = [ config.get('executables', 'cdrdao'), "disk-info", "--device", config.get('CD-writer', 'device'), "--driver", config.get('CD-writer', 'driver'), ] command_process = subprocess.Popen( command_args, close_fds=True, stdout=subprocess.PIPE, stderr=open(os.devnull)) for line in command_process.stdout: if line.startswith('Total Capacity'): if line.split(':')[1].strip() == 'n/a': return None return line.split()[6].split('/')[0] return None def prog_intro(mode=None): """ Output program introduction message for specified mode. """ if config.getboolean('general', 'ask_root'): if not pwd.getpwuid(os.geteuid())[0] == "root": if not console.ask_yesno(_( 'You are not superuser (root).' ' Do you still want to continue'), True): sys.exit() print _( 'Burn v.%(version)s ' 'Written by %(author_name)s.') % vars(burnlib.version) print _('Burn until recorded, now!') print _( 'This software comes with absolutely no warranty!' ' Use at your own risk!') print _('Burn is free software.') print _('See software updates at .') % vars(burnlib) print mode_title = ContentMode.titles.get(mode) if mode_title is not None: print mode_title + "..." print def show_examples(option, opt, value, parser): """Show examples for quick startup""" print "# burn -D -p /etc/" print _( ' Creates a CD/DVD with /etc/ contents. (you will find files' ' and directories contained in /etc in CD\'s root)') print "# burn -D -p /home/bigpaul/video/summer_2003/spain.tar.gz" print _(' Creates a CD/DVD with spain.tar.gz in CD\'s root') print "# burn -D -r /etc/" print _( ' Creates a CD/DVD containing the whole /etc/ directory.' ' (-r preserves path)') print "# burn -D -c /mail_2003 /home/bigpaul/Mail -p /boot/vmli*" print _( ' Creates a CD/DVD containing the whole /home/bigpaul/Mail' ' renamed into /mail_2003. (-c changes path name).' ' This command also adds in CD\'s root every vmli* file' ' in /boot/ directory') print "# burn -I -n image.iso" print _(' Burns image.iso') print "# burn -C" print _(' Copy CDs (disk at once).') print "# burn -A -a *.wav" print _(' Creates an Audio CD. Tracks come from wav files') print "# burn -A -a *.mp3" print _(' Creates an Audio CD. Tracks come from mp3 files') print "# burn -A -a *.ogg" print _(' Creates an Audio CD. Tracks come from Ogg Vorbis files') print "# burn -A -a *.mp3 file.ogg track01.wav" print _(' Creates an Audio CD. Tracks come from .wav, .ogg, .mp3 files') sys.exit() def varargs(option, opt, value, parser): """Callback function to manage multiple arguments (or shell expansion)""" assert value is None step = 0 value = [] rargs = parser.rargs while rargs: step = step + 1 arg = rargs[0] if ((arg[:2] == "--" and len(arg) > 2) or (arg[:1] == "-" and len(arg) > 1 and arg[1] != "-")): if step == 1: print _( '%(option)s is an option that takes one or more arguments.' ' So you can\'t put %(arg)s after it') % vars() sys.exit() else: break else: value.append(arg) del rargs[0] setattr(parser.values, option.dest, value) class ISO: "ISO class" def __init__(self): """ Set up a new instance. """ self.path_o = [] self.genisoimage_args = [] self.windows = config.getboolean('ISO', 'windows_read') self.tempdir = config.get('ISO', 'tempdir') self.image_name = config.get('ISO', 'image') self.mount_dir = config.get('ISO', 'mount_dir') self.dest = os.path.normpath(self.tempdir + self.image_name) def create(self): """ Execute data track recording command. """ print _('Creating temporary image: '), self.dest pbar = console.ProgressBar(width=30) command_process = subprocess.Popen( self.genisoimage_args, close_fds=True, stdin=subprocess.PIPE, stdout=open(os.devnull), stderr=subprocess.PIPE) progress = 0 for line in command_process.stderr: if "done, estimate finish" in line: progress = int(float(line.split()[0][:-1])) pbar.amount = progress pbar.update_display() pbar.amount = 100 pbar.update_display() def destroy(self): """remove the file""" os.remove(self.dest) def ask_mount(self, image): """asks if user wants to mount image""" if console.ask_yesno(_( 'Do you want to see image\'s contents before proceeding' ), True): self.mount(image) def freespace(self): """return free disk space""" space = os.statvfs(self.tempdir) return (long(space[statvfs.F_BAVAIL]) * \ long(space[statvfs.F_BSIZE]))/1048576 def mount(self, image): """mount image in self.mount_dir""" mount_dir = self.mount_dir if os.path.exists(mount_dir) and os.path.isdir(mount_dir): command_args = ["mount", "-o", "loop", image, mount_dir] if subprocess.call(command_args): print _( 'Unable to mount %(image)s. Please check if you have' ' permissions to mount it on %(mount_dir)s') % vars() sys.exit() self.ask_after_mount(self.mount_dir) else: err(self.mount_dir + _(' is not valid as a mount point')) sys.exit() command_args = ["umount", self.mount_dir] subprocess.call(command_args) def ask_after_mount(self, dirname): """Choose what to do with the mounted image...""" prompt = _( '\n\nPlease choose:\n\n' '1. Print every file with path\n' '2. Print directory tree structure\n' '3. Ok, finished... Let\'s burn\n' '> ') mount_choice = raw_input(prompt) if mount_choice in ('1'): #option num. 1 ---> every file print for root, dirs, files in os.walk(dirname): for file_path in files: print os.path.join(root, file_path) self.ask_after_mount(dirname) #return True if mount_choice in ('2'): #option num. 2 ---> tree structure print for root, dirs, files in os.walk(dirname): for directory in dirs: print os.path.join(root, directory) self.ask_after_mount(dirname) if mount_choice in ('3'): #option num. 3 ---> done return True def ask_multisession(self): """Asks if user wants to add stuff to a multisession CD or if he wants to create a new multisession CD from scratch.""" prompt = _( '\n\nPlease choose:\n\n' '1. Create new multisession CD from a blank media\n' '2. Append data to an existant multisession CD\n' '> ') multisession_cd = raw_input(prompt) if multisession_cd in ('1'): #option num. 1 new multisession return 1 if multisession_cd in ('2'): #option num. 2 already multisession return 2 def ask_remove(self): """asks to remove image""" print print self.dest, _(' is the image file created...') if console.ask_yesno(_('Do you want me to remove it'), False): print _('Removing '), self.dest, "..." os.remove(self.dest) return True else: return False def first_ask_remove(self): """asks to remove image at the very beginning""" print print _( 'Warning. there is already a temporary image file' ' named %(dest)s.') % vars(self) if console.ask_yesno(_('Do you want me to remove it'), False): print _('Removing '), self.dest, "..." os.remove(self.dest) return True else: return False class CDROM: "CDROM class" def __init__(self, options): """ Set up a new instance. """ self.wodim_args = [] self.wodim = config.get('executables', 'wodim') self.cdrdao = config.get('executables', 'cdrdao') self.speed = config.getint('CD-writer', 'speed') self.device = config.get('CD-writer', 'device') self.source_device = config.get('CD-reader', 'device') self.driver = config.get('CD-writer', 'driver') self.source_driver = config.get('CD-reader', 'driver') self.burnfree = config.getboolean('CD-writer', 'burnfree') self.content_mode = options.mode self.multisession = options.multisession self.simulate = options.simulate self.eject = options.eject self.iso = ISO() def compute_media_size(self): """ Get the storage capacity of the media. """ if config.getboolean('Media', 'media-check'): empty = check_media_empty() if not empty: if not self.multisession: print _('Error. Please insert a blank CD/DVD.') command_args = [ self.wodim, "-eject", "dev=%(device)s" % vars(self)] subprocess.call( command_args, stdout=open(os.devnull), stderr=open(os.devnull)) sys.exit() self.size = get_media_capacity() if not self.size: print _( "Error. unknown media capacity. " "Using configuration default.") self.size = config.getint('Media', 'size') else: self.size = config.getint('Media', 'size') def size_compare(self, tobeburned, space_needed): """Checks free space for temporary files and CD oversize""" free_disk_space = int(self.iso.freespace()) self.compute_media_size() print print _('To be burned: '), "\t\t\t", tobeburned / 1048576, "Mb" print _('Disk space needed: '), "\t\t", space_needed/1048576, "Mb" print _('Media capacity: '), "\t\t", self.size, "Mb" if self.content_mode is not ContentMode.iso: print _('Free disk space: '), "\t\t", free_disk_space, "Mb" if space_needed == 0: space_needed = tobeburned if self.content_mode is not ContentMode.iso: if (space_needed/1048576) > free_disk_space: if self.content_mode is ContentMode.data: print _('You do not have enough free disk space'), \ " (", free_disk_space, " Mb )", \ _('to create temporary image file '), \ "( ", tobeburned / 1048576, " Mb )" elif self.content_mode is ContentMode.audio: print _('You do not have enough free disk space'), \ " (", free_disk_space, " Mb )", \ _('to create temporary audio files '), \ "( ", tobeburned / 1048576, " Mb )" sys.exit() if (tobeburned / 1048576) > int(self.size): if not console.ask_yesno(_( 'It seems you are going to burn more than media\'s capacity.\n' 'Do you still want to continue'), False): sys.exit() return True def make_command_args(self): """ Generate command-line arguments for track recording. """ self.wodim_args = [self.wodim, "-v", "-pad"] if self.simulate: self.wodim_args.append("-dummy") if self.eject: self.wodim_args.append("-eject") if self.speed: self.wodim_args.append("speed=%(speed)s" % vars(self)) else: print _('no burning speed defined, using 2x') self.wodim_args.append("speed=2") if self.device: self.wodim_args.append("dev=%(device)s" % vars(self)) else: print _('no device specified.') sys.exit() if self.burnfree: self.wodim_args.append("driveropts=burnfree") if self.multisession: self.wodim_args.append("-multi") if self.content_mode in [ContentMode.data, ContentMode.iso]: self.wodim_args.extend(["-data", self.iso.dest]) def create(self): """ Execute track recording command. """ print if config.getboolean('general', 'pause'): print _('Press a key to begin recording...') console.getch() print _('Please wait...') subprocess.call(self.wodim_args) def double_dao_create(self, command_args): """ Execute disk-at-once copy with two drives (reader and writer). """ print print _('Place the source CD in the CD drive') print _('and place a blank media in the recording unit.') print _('Press a key to begin on-the-fly copy') console.getch() subprocess.call(command_args) def single_dao_create(self, command_args): """ Execute disk-at-once copy with one drive. """ print print _('Place source CD in the recording unit.') print _('Press a key to begin reading...') console.getch() subprocess.call(command_args) def another_copy(self): """burn image untill user says no""" while console.ask_yesno(_( 'Do you want to use this image to make another copy now?' ), False): self.make_command_args() self.create() def main(): """ Program mainline procedure. """ usage = """%prog -MODE [general_option] [mode_option] ... For quick start you can get common examples with: burn -e """ parser = optparse.OptionParser(usage=usage) parser.add_option( "-e", "--examples", action="callback", callback=show_examples, dest="examples", help=_('show examples for quick startup')) #Modes mode_options = optparse.OptionGroup( parser, _('Main burn MODES'), _('They _have_ to be the first argument after program name')) mode_options.add_option( "-D", "--data-cd", action="store_const", dest="mode", const=ContentMode.data, help=_('creates a Data CD/DVD.')) mode_options.add_option( "-I", "--iso-cd", action="store_const", dest="mode", const=ContentMode.iso, help=_('creates a CD/DVD from ISO.')) mode_options.add_option( "-C", "--copy-cd", action="store_const", dest="mode", const=ContentMode.copy, help=_('copy CD/DVD.')) mode_options.add_option( "-A", "--audio-cd", action="store_const", dest="mode", const=ContentMode.audio, help=_('creates an Audio CD from .wav, .mp3 and .ogg files.')) #General options general_option = optparse.OptionGroup( parser, "General options", _( 'These options could be used for every burn mode' ' unless stated otherwise')) general_option.add_option( "-s", "--simulate", action="store_true", dest="simulate", help=_('This option will perform a burn simulation.')) general_option.add_option( "-j", "--eject", action="store_true", dest="eject", help=_( 'This option will eject disk when burn process is over.')) general_option.add_option( "--dao", action="store_true", dest="dao", help=_( 'Enable disk at once. Enabling this option, you will not have' ' 2 seconds gap between tracks.')) #DATA-CD Options data_cd = optparse.OptionGroup(parser, _('Data CD Mode (-D) options'), _('Data CD: adds files and directories.')) data_cd.add_option( "-p", "--path", action="callback", callback=varargs, dest="path", help=_( 'add file/s or path\'s content to CD-ROM/DVD\'s root.' ' e.g.: -p /cvs/myproj/ . In this example we will find' ' CD-ROM/DVD\'s root filled with /cvs/myproj/ contents, but' ' no /cvs/myproj/ will be created.')) data_cd.add_option( "-r", "--preserve-path", action="callback", callback=varargs, dest="preserve_path", help=_( 'add file/s or path\'s content to CD-ROM/DVD preserving' ' original path. e.g.: -r /cvs/myproj/ . In this' ' example we will find /cvs/myproj/ in CD-ROM/DVD\'s root.')) data_cd.add_option( "-x", "--exclude-path", #action="append", type="string", dest="exclude_path", action="callback", callback=varargs, dest="exclude_path", help=_( 'every file or directory matching this string' ' will not be included.')) data_cd.add_option( "-c", "--change-path", action="append", nargs=2, type="string", dest="change_path", help=_( 'usage: -c . With this option,' ' old_path will be named new_path in CD-ROM/DVD.' ' e.g.: -c /my_home/2004_Jan/ /home/bigpaul/ .' ' Thus /home/bigpaul/ will be named /my_home/2004_Jan/' ' in CD-ROM/DVD.')) data_cd.add_option( "-l", "--follow-symlink", action="store_true", dest="follow_symlink", help=_('this option allows burn to follow symbolic link directories')) data_cd.add_option( "-m", "--multisession", action="store_true", dest="multisession", help=_('this option allows multisession CDs')) #ISO-CD Options iso_cd = optparse.OptionGroup(parser, "ISO CD Mode (-I) options", _('Creates a CD-ROM/DVD from an existing image.')) iso_cd.add_option( "-n", "--name", action="store", type="string", dest="iso_name", help=_('image name')) #COPY-CD Options copy_cd = optparse.OptionGroup( parser, _('Copy CD Mode (-C) options'), _( 'If you have both a reader and a recording unit' ' you can perform a copy on-the-fly. You can also copy a CD' ' even if you only have the recording unit.')) #AUDIO-CD Options audio_cd = optparse.OptionGroup( parser, _('Audio CD Mode (-A) options'), _( 'Audio CD is used to create an audio CD-ROM' ' from .wav, .ogg and .mp3 files. You can can use -a option' ' to perform an Audio CD from different source audio files.')) audio_cd.add_option( "-a", "--audio-file", action="callback", callback=varargs, dest="general_audio_file_list", help=_( '.wav, .mp3, .ogg file/s. Files must have extensions' ' (no matter if they are upper or lowercase).')) audio_cd.add_option( "--audio-list", action="store", type="string", dest="file_list", help=_('m3u playlist or file with one audio file per line.')) audio_cd.add_option( "--clear-audiotemp", action="store_true", dest="clear_audio_temp", help=_('remove temporary audio files.')) audio_cd.add_option( "--no-gaps", action="store_true", dest="nogaps", help=_( 'Enable disk at once. Enabling this option, you will not have' ' 2 seconds gap between tracks.')) parser.add_option_group(mode_options) parser.add_option_group(general_option) parser.add_option_group(data_cd) parser.add_option_group(iso_cd) parser.add_option_group(copy_cd) parser.add_option_group(audio_cd) (options, args) = parser.parse_args() if len(sys.argv) > 1: check_main_option(sys.argv[1]) else: prog_intro() parser.print_help() sys.exit() configure.read_from_files() prog_intro(options.mode) cdrom = CDROM(options) if options.mode is ContentMode.data: print _('Checking files, directories and disk usage. Please wait...') print path = '' path_preserved = '' path_changed = '' paths_excluded = [] size = 0 first_time_multisession = 0 if options.path: for directory in options.path: if os.path.exists(directory): abspath = os.path.abspath(directory) path = path + '\'' + abspath + '\'' + ' ' if os.path.isfile(abspath): size = size + os.path.getsize(abspath) elif os.path.isdir(abspath): size = size + du(abspath, 0, options.follow_symlink) else: err(abspath + _(': no such file or directory.')) else: err_nf(directory) if options.preserve_path: for directory in options.preserve_path: if os.path.exists(directory): abspath = os.path.abspath(directory) if os.path.isdir(abspath): size = size + du(abspath, 0, options.follow_symlink) elif os.path.isfile(abspath): size = size + os.path.getsize(abspath) else: err(abspath + _(': no such file or directory.')) sys.exit() path_preserved = path_preserved \ + '\'' + abspath + '\'' + '=' \ + '\'' + abspath + '\'' + ' ' else: err_nf(directory) if options.change_path: for (new_path, old_path) in options.change_path: if os.path.exists(old_path): abspath = os.path.abspath(old_path) if os.path.isfile(abspath): size += os.path.getsize(abspath) elif os.path.isdir(abspath): size += du(abspath, 0, options.follow_symlink) else: err(abspath + _(': no such file or directory.')) sys.exit() path_changed = path_changed \ + '\'' + new_path + '\'' + '=' \ + '\'' + abspath + '\'' + ' ' else: err_nf(old_path) print _( 'nothing will be done for %(old_path)s -> %(new_path)s' ) % vars() if options.exclude_path: testsize = size for directory in options.path: if os.path.isdir(directory): for exclude_path in options.exclude_path: for root, dirs, files in os.walk(directory): for file_name in files: if fnmatch.fnmatch(file_name, exclude_path): if os.path.exists( os.path.join(root, file_name)): if os.path.isfile( os.path.join(root, file_name)): size = size - os.path.getsize( os.path.join(root, file_name)) paths_excluded.append( os.path.join(root, file_name)) for subdir in dirs: if fnmatch.fnmatch(subdir, exclude_path): if os.path.exists( os.path.join(root, subdir)): size = size - du( os.path.join(root, subdir), 0, options.follow_symlink) paths_excluded.append( os.path.join(root, subdir)) print print _('Size without exclusions: '), "\t", testsize/1048576, "Mb" global_path = path + path_preserved + path_changed if global_path == '': err(_('Nothing to be burned...')) sys.exit() cdrom.size_compare(size, size) cdrom.iso.genisoimage_args.extend( [config.get('executables', 'genisoimage'), "-R"]) if os.path.exists(cdrom.iso.tempdir): cdrom.iso.genisoimage_args.extend(["-o", cdrom.iso.dest]) else: err(_('Error: ') + cdrom.iso.tempdir + _(' does not exist')) sys.exit() if cdrom.iso.windows: cdrom.iso.genisoimage_args.extend(["-J", "-joliet-long"]) for path_excluded in paths_excluded: cdrom.iso.genisoimage_args.extend(["-x", path_excluded]) if options.multisession: multisession_choose = cdrom.iso.ask_multisession() if multisession_choose == 2: #ciao print _( 'Place target CD in CD/DVD writer unit and press a key...') console.getch() print _('Please wait...') command_args = [ config.get('executables', 'wodim'), "-msinfo", "dev=%(device)s" % vars(cdrom)] command_process = subprocess.Popen( command_args, stdout=subprocess.PIPE, stderr=open(os.devnull)) msinfo = command_process.communicate()[0] cdrom.iso.genisoimage_args.extend( ["-C", msinfo, "-M", cdrom.device]) elif not multisession_choose == 1: sys.exit() else: first_time_multisession = 1 cdrom.iso.genisoimage_args.extend(["-graft-points", global_path]) cdrom.make_command_args() if os.path.exists(cdrom.iso.dest): if not cdrom.iso.first_ask_remove(): cdrom.another_copy() sys.exit() cdrom.iso.create() if first_time_multisession == 1: cdrom.iso.ask_mount(cdrom.iso.dest) cdrom.create() if os.path.exists(cdrom.iso.dest): if not cdrom.iso.ask_remove(): cdrom.another_copy() sys.exit() if options.mode is ContentMode.iso: if os.path.exists(options.iso_name): if cdrom.size_compare(os.path.getsize(options.iso_name), 0): print cdrom.iso.dest = options.iso_name cdrom.iso.ask_mount(options.iso_name) cdrom.make_command_args() cdrom.create() if os.path.exists(cdrom.iso.dest): if not cdrom.iso.ask_remove(): cdrom.another_copy() else: sys.exit() else: err_nf(options.iso_name) sys.exit() sys.exit() if options.mode is ContentMode.copy: cdrom.compute_media_size() single_drive_mode = 0 if cdrom.device == cdrom.source_device or cdrom.source_device == '': single_drive_mode = 1 #print single_drive_mode cdrdao_args = [config.get('executables', 'cdrdao')] if single_drive_mode == 1: cdrdao_args.append("copy") if options.simulate: cdrdao_args.append("--simulate") if options.eject: cdrdao_args.append("--eject") cdrdao_args.extend(["--datafile", cdrom.iso.dest]) cdrdao_args.extend(["--device", cdrom.device]) if not cdrom.driver == '': cdrdao_args.extend(["--driver", cdrom.driver]) cdrdao_args.extend(["--speed", str(cdrom.speed)]) cdrdao_args.append("--fast-toc") cdrom.single_dao_create(cdrdao_args) else: cdrdao_args.append("copy") if options.simulate: cdrdao_args.append("--simulate") if options.eject: cdrdao_args.append("--eject") cdrdao_args.extend(["--device", cdrom.device]) if not cdrom.driver == '': cdrdao_args.extend(["--driver", cdrom.driver]) cdrdao_args.extend(["--source-device", cdrom.source_device]) if not cdrom.source_driver == '': cdrdao_args.extend(["--source-driver", cdrom.source_driver]) cdrdao_args.extend(["--speed", str(cdrom.speed)]) cdrdao_args.append("--on-the-fly") cdrdao_args.append("--fast-toc") cdrom.double_dao_create(cdrdao_args) sys.exit() if options.mode is ContentMode.audio: print _('Audio file processing. Please wait...') print audio_list = [] to_be_removed = [] old_temp_wavs = [] file_list = [] track_type = '' size = 0 track_number = 0 track_number2 = 0 mp3_ogg_size = 0 counter = 1000 total_audio_time = 0 #61196 wav header???? #176400 kb 1 wav second comes from: #44100 * 16 * 2bit / 8 = byte list_dir = os.listdir(cdrom.iso.tempdir) if options.clear_audio_temp: for file_path in list_dir: if re.compile("^burn_1").search(file_path[:5], 0): old_temp_wavs.append( os.path.normpath(cdrom.iso.tempdir + file_path)) if old_temp_wavs: print for old_wavs in old_temp_wavs: print _('removing '), old_wavs, "..." os.remove(old_wavs) if options.general_audio_file_list: file_list = options.general_audio_file_list if options.file_list: file_list.extend(get_list_from_file(options.file_list)) for file_path in file_list: base, ext = os.path.splitext(file_path) ext = ext.lower() if ext == '.ogg' or ext == '.mp3': mp3_ogg_size += ( audio.file_duration(file_path) * 176400) if ext == '.wav': size += os.path.getsize(file_path) if ext != '.ogg' and ext != '.mp3' and ext != '.wav': print file_path, _(': not a regular audio file. Skipped') cdrom.size_compare((size+mp3_ogg_size), mp3_ogg_size) print list_dir = os.listdir(cdrom.iso.tempdir) old_temp_wavs = [] for file_path in list_dir: if re.compile("^burn_1").search(file_path[:5], 0): old_temp_wavs.append( os.path.normpath(cdrom.iso.tempdir + file_path)) if old_temp_wavs: print for wav_path in old_temp_wavs: print wav_path if console.ask_yesno(_( 'You have old burn audio files in temporary directory.' ' Remove these files and continue' ), False): for oldwavs in old_temp_wavs: print _('removing '), oldwavs, "..." os.remove(oldwavs) else: sys.exit() print print "---------------------------------------------" print "Burn - " + _('Track summary') print "---------------------------------------------" for file_path in file_list: track_number = track_number + 1 base, ext = os.path.splitext(file_path) ext = ext.lower() if ext == '.mp3' or ext == '.ogg': if os.path.exists(file_path): abspath = os.path.abspath(file_path) if os.path.isfile(abspath): info = audio.FileInfo(abspath) if info.title: print track_number, ")\t", \ info.duration, "-", info.title else: print track_number, ")\t", \ info.duration, "-", os.path.abspath(file_path) total_audio_time += info.total_time else: print track_number, ")\t", audio.compute_duration( os.path.getsize(os.path.abspath(file_path)) / 176400), \ "-", os.path.abspath(file_path) total_audio_time += os.path.getsize( os.path.abspath(file_path)) / 176400 # print "Total audio time: ", int(total_audio_time) print print _('Total Audio-CD: '), audio.compute_duration( int(total_audio_time)) print if config.getboolean('general', 'external_decoding'): print _('Performing audio decoding with external decoder.') ogg_decoder = config.get('executables', 'ogg_decoder') mp3_decoder = config.get('executables', 'mp3_decoder') ogg_decoder_option = config.get( 'executables', 'ogg_decoder_option') mp3_decoder_option = config.get( 'executables', 'mp3_decoder_option') else: print _('Performing audio decoding with burn\'s native functions.') for file_path in file_list: track_number2 = track_number2 + 1 base, ext = os.path.splitext(file_path) ext = ext.lower() if ext == '.mp3' or ext == '.ogg': counter = counter + 1 if ext == '.mp3': track_type = 'MP3' else: track_type = 'OGG' if os.path.exists(file_path): abspath = os.path.abspath(file_path) if os.path.isfile(abspath): #Shows full path ogg files print _( "[%(track_number2)d/%(track_number)d]" " %(track_type)s\tProcessing %(abspath)s") % vars() info = audio.FileInfo(abspath) #Shows ID3 TAGS #print "\t\tTitle: \t\t",info.title #print "\t\tAlbum: \t\t",info.album #print "\t\tArtist: \t",info.artist #Convert mp3/ogg file in tempdir/file.[mp3|ogg].wav if ext == '.mp3': if config.getboolean('general', 'external_decoding'): wav_name = "burn_%(counter)d.wav" % vars() wav_path = os.path.normpath(os.path.join( cdrom.iso.tempdir, wav_name)) command_args = [mp3_decoder] command_args.extend( shlex.split(mp3_decoder_option)) command_args.extend([wav_path, abspath]) subprocess.call(command_args) else: dev = ao.AudioDevice( 'wav', filename=( os.path.normpath( cdrom.iso.tempdir + 'burn_' + repr(counter)) + '.wav'), overwrite=True) pbar = console.ProgressBar(width=30) audio_buffer = mad.MadFile(abspath) old_progress = 0 while True: buf = audio_buffer.read() if buf is None: break progress = \ audio_buffer.current_time() * 100 / \ audio_buffer.total_time() if progress > old_progress: pbar.amount = progress pbar.update_display() old_progress = progress dev.play(buf, len(buf)) audio_list.append(os.path.normpath( cdrom.iso.tempdir + 'burn_' + repr(counter)) + '.wav') to_be_removed.append(os.path.normpath( cdrom.iso.tempdir + 'burn_' + repr(counter)) + '.wav') elif ext == '.ogg': size = 4096 if config.getboolean('general', 'external_decoding'): wav_name = "burn_%(counter)d.wav" % vars() wav_path = os.path.normpath(os.path.join( cdrom.iso.tempdir, wav_name)) command_args = [ogg_decoder] command_args.extend( shlex.split(ogg_decoder_option)) command_args.extend([wav_path, abspath]) subprocess.call(command_args) else: dev = ao.AudioDevice( 'wav', filename=os.path.normpath( cdrom.iso.tempdir + 'burn_' + repr(counter)) + '.wav', overwrite=True) pbar = console.ProgressBar(width=30) audiofile = ogg.vorbis.VorbisFile(abspath) old_progress = 0 while True: (buf, bytes, bit) = audiofile.read(size) if bytes == 0: break progress = \ audiofile.time_tell() * 100 / \ audiofile.time_total(-1) if progress > old_progress: pbar.amount = progress pbar.update_display() old_progress = progress dev.play(buf, bytes) audio_list.append(os.path.normpath( cdrom.iso.tempdir + 'burn_' + repr(counter)) +'.wav') to_be_removed.append(os.path.normpath( cdrom.iso.tempdir + 'burn_' + repr(counter)) +'.wav') else: err(abspath + _(': not a valid audio file.')) if ext == '.wav': track_type = 'WAV' if os.path.exists(file_path): abspath = os.path.abspath(file_path) print _( "[%(track_number2)d/%(track_number)d]" " %(track_type)s\tProcessing %(abspath)s") % vars() audio_list.append(abspath) cdrom.make_command_args() if options.nogaps: cdrom.wodim_args.append("-dao") cdrom.wodim_args.append("-audio") cdrom.wodim_args.extend(audio_list) #burning CD cdrom.create() if config.getboolean('general', 'pause'): while console.ask_yesno(_( 'Do you want to use processed audio files to create' ' another Audio CD now'), False): cdrom.create() else: while console.ask_yesno(_( 'Write another copy (insert a blank disc now)'), False): console.getch() cdrom.create() #removing temp audio files for file_path in to_be_removed: if os.path.exists(file_path): print _('removing '), file_path, "..." os.remove(file_path) sys.exit() if __name__ == '__main__': try: main() except KeyboardInterrupt: print print _('burn: exiting now...') # Local variables: # mode: python # coding: utf-8 # End: # vim: filetype=python fileencoding=utf-8 : burn-0.4.6/burnlib/device.py0000664000175000017500000000306311314132111015563 0ustar bignosebignose# -*- coding: utf-8 -*- # burnlib/device.py # # Copyright © 2009 Ben Finney . # Copyright © 2003–2009 Gaetano Paolone . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. """ Functionality for working with optical media devices. """ import subprocess def device_list_output(): """ Return output of query to optical media devices. """ process = subprocess.Popen( ["wodim", "-devices"], stdout=subprocess.PIPE) process_stdout = process.communicate()[0] return process_stdout def bus_list_output(): """ Return output of query to optical media device bus. """ process = subprocess.Popen( ["wodim", "-scanbus"], stdout=subprocess.PIPE) process_stdout = process.communicate()[0] return process_stdout # Local variables: # mode: python # coding: utf-8 # End: # vim: filetype=python fileencoding=utf-8 : burn-0.4.6/burnlib/__init__.py0000664000175000017500000000333311223400473016073 0ustar bignosebignose# -*- coding: utf-8 -*- # burnlib/__init__.py # # Copyright © 2009 Ben Finney . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. # The module docstring is used as input to distutils for the short and # long descriptions. Due to , # distutils in Python 2.5 cannot handle any encoding other than ASCII; # once Python 2.5 is no longer required for building the distribution, # this docstring can be properly encoded in full UTF-8. """ Command-line tool for writing optical media. 'burn' is a command-line tool to create audio discs from MP3, Ogg Vorbis, or WAV files, to backup data files, to create discs from ISO-9660 images, and to copy discs on-the-fly. It performs any of its functions in a single command, without requiring preparatory filesystem creation, etc. The program can compute if there is necessary free space for temporary files (images and audio files), warn if size is bigger than disc capacity, and manage multisession discs. """ _url = "http://www.bigpaul.org/burn/" burn-0.4.6/example/0000755000175000017500000000000011314307442013757 5ustar bignosebignoseburn-0.4.6/example/burn.conf0000664000175000017500000000310211237733167015605 0ustar bignosebignose# burn.conf # Configuration for Burn optical media writer front-end. [general] # Prompt to continue if executed as non-root user? ask_root: yes # Use external programs for decoding compressed audio? external_decoding: no [executables] # Path to media image writer program. wodim: /usr/bin/wodim # Path to media disc-at-once read/write program. cdrdao: /usr/bin/cdrdao # Path to media image generator program. genisoimage: /usr/bin/genisoimage # Path to decoder program for MP3 data. mp3_decoder: /usr/bin/mpg321 # Command options for MP3 decoder program. mp3_decoder_option: -q -w # Path to decoder program for Ogg Vorbis data. ogg_decoder: /usr/bin/ogg123 # Command options for Ogg Vorbis decoder program. ogg_decoder_option: -q -d wav -f [ISO] # Path to create temporary image files. tempdir: /tmp/ # File name for temporary image files. image: burn_image.iso # Add Joliet data (to make a MS Windows compatible filesystem)? windows_read: yes # Mount point (directory path) for checking image filesystem. mount_dir: /mnt/ [CD-writer] # Device node path for optical media writer. device: /dev/cdrom # Speed setting for write operations. speed: 4 # Value for ‘driver’ setting when invoking ‘cdrdao’ command. driver: generic-mmc # Use Buffer Underrun Free writing support? burnfree: yes [CD-reader] # Device node path for optical media reader. device: /dev/cdrom # Value for ‘driver’ setting when invoking ‘cdrdao’ command. driver: [Media] # Detect target media capacity before writing? media-check: no # Default media capacity (MB) for media writer. size: 700 burn-0.4.6/MANIFEST.in0000664000175000017500000000022411237733277014077 0ustar bignosebignoseinclude MANIFEST.in setup.py setup.cfg include README copyright changelog include example/burn.conf recursive-include bin * recursive-include doc * burn-0.4.6/bin/0000755000175000017500000000000011314307442013074 5ustar bignosebignoseburn-0.4.6/bin/burn-configure0000775000175000017500000000160511314132111015740 0ustar bignosebignose#! /bin/bash # bin/burn-configure # # Copyright © 2009 Ben Finney . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. exec python -c "from burnlib import interactive_configure; interactive_configure.main()" "$@" burn-0.4.6/bin/burn0000775000175000017500000000153111223353146013773 0ustar bignosebignose#! /bin/bash # bin/burn # # Copyright © 2009 Ben Finney . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. exec python -c "from burnlib import burn; burn.main()" "$@" burn-0.4.6/burn.egg-info/0000755000175000017500000000000011314307442014764 5ustar bignosebignoseburn-0.4.6/burn.egg-info/requires.txt0000664000175000017500000000003111314307442017360 0ustar bignosebignosesetuptools docutils >=0.6burn-0.4.6/burn.egg-info/top_level.txt0000664000175000017500000000001011314307442017507 0ustar bignosebignoseburnlib burn-0.4.6/burn.egg-info/dependency_links.txt0000664000175000017500000000000111314307442021034 0ustar bignosebignose burn-0.4.6/burn.egg-info/not-zip-safe0000664000175000017500000000000111223377630017221 0ustar bignosebignose burn-0.4.6/burn.egg-info/SOURCES.txt0000664000175000017500000000134611314307442016656 0ustar bignosebignoseMANIFEST.in README changelog copyright setup.cfg setup.py bin/burn bin/burn-configure burn.egg-info/PKG-INFO burn.egg-info/SOURCES.txt burn.egg-info/dependency_links.txt burn.egg-info/not-zip-safe burn.egg-info/requires.txt burn.egg-info/top_level.txt burnlib/__init__.py burnlib/audio.py burnlib/burn.py burnlib/configure.py burnlib/console.py burnlib/device.py burnlib/interactive_configure.py burnlib/version.py doc/AUTHORS doc/BUGS doc/INSTALL doc/KNOWN_BUGS doc/TODO doc/WISHLIST doc/burn-configure.1.txt doc/burn-flame.jpeg doc/burn.1.txt doc/burn.css doc/docutils.conf doc/index.txt example/burn.conf test/__init__.py test/test_audio.py test/test_configure.py test/test_console.py test/test_device.py test/test_interactive_configure.pyburn-0.4.6/burn.egg-info/PKG-INFO0000664000175000017500000000237011314307442016065 0ustar bignosebignoseMetadata-Version: 1.0 Name: burn Version: 0.4.6 Summary: Command-line tool for writing optical media. Home-page: http://www.bigpaul.org/burn/ Author: Ben Finney Author-email: ben+python@benfinney.id.au License: GPL-2+ Description: 'burn' is a command-line tool to create audio discs from MP3, Ogg Vorbis, or WAV files, to backup data files, to create discs from ISO-9660 images, and to copy discs on-the-fly. It performs any of its functions in a single command, without requiring preparatory filesystem creation, etc. The program can compute if there is necessary free space for temporary files (images and audio files), warn if size is bigger than disc capacity, and manage multisession discs. Keywords: burn,cd,dvd,media Platform: UNKNOWN Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: End Users/Desktop Classifier: Environment :: Console Classifier: Operating System :: POSIX Classifier: Operating System :: MacOS Classifier: Programming Language :: Python Classifier: Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Writing Classifier: Topic :: System :: Archiving Classifier: Topic :: Utilities burn-0.4.6/PKG-INFO0000644000175000017500000000237011314307442013423 0ustar bignosebignoseMetadata-Version: 1.0 Name: burn Version: 0.4.6 Summary: Command-line tool for writing optical media. Home-page: http://www.bigpaul.org/burn/ Author: Ben Finney Author-email: ben+python@benfinney.id.au License: GPL-2+ Description: 'burn' is a command-line tool to create audio discs from MP3, Ogg Vorbis, or WAV files, to backup data files, to create discs from ISO-9660 images, and to copy discs on-the-fly. It performs any of its functions in a single command, without requiring preparatory filesystem creation, etc. The program can compute if there is necessary free space for temporary files (images and audio files), warn if size is bigger than disc capacity, and manage multisession discs. Keywords: burn,cd,dvd,media Platform: UNKNOWN Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: End Users/Desktop Classifier: Environment :: Console Classifier: Operating System :: POSIX Classifier: Operating System :: MacOS Classifier: Programming Language :: Python Classifier: Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Writing Classifier: Topic :: System :: Archiving Classifier: Topic :: Utilities burn-0.4.6/test/0000755000175000017500000000000011314307442013303 5ustar bignosebignoseburn-0.4.6/test/test_configure.py0000644000175000017500000003776711314132111016706 0ustar bignosebignose# -*- coding: utf-8 -*- # test/test_configure.py # Part of ‘burn’, a tool for writing optical media. # # Copyright © 2009 Ben Finney . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. """ Unit test module for ‘configure’ module. """ import __builtin__ import doctest import ConfigParser import textwrap import functools import operator import tempfile from StringIO import StringIO import errno import nose.tools import minimock from burnlib import configure import burnlib.version class config_file_paths_TestCase(object): """ Test cases for ‘config_file_paths’ object. """ def test_items_are_strings(self): """ Every item in the list should be a string. """ string_items = [ item for item in configure.config_file_paths if isinstance(item, basestring)] nose.tools.assert_equal(string_items, configure.config_file_paths) class config_TestCase(object): """ Test cases for ‘config’ object. """ def test_config_is_safe_config_parser(self): """ Should be a SafeConfigParser instance. """ nose.tools.assert_true( isinstance(configure.config, ConfigParser.SafeConfigParser)) def test_config_has_expected_defaults(self): """ Should have expected default settings. """ expect_defaults = { 'ask_root': "yes", 'external_decoding': "no", 'pause': "yes", 'wodim': "/usr/bin/wodim", 'cdrdao': "/usr/bin/cdrdao", 'genisoimage': "/usr/bin/genisoimage", 'mp3_decoder': "/usr/bin/mpg321", 'mp3_decoder_option': "-q -w", 'ogg_decoder': "/usr/bin/ogg123", 'ogg_decoder_option': "-q -d wav -f", 'tempdir': "/tmp/", 'image': "burn_image.iso", 'windows_read': "yes", 'mount_dir': "/mnt/", 'device': "/dev/cdrom", 'speed': "4", 'driver': "generic-mmc", 'burnfree': "yes", 'media-check': "no", 'size': "700", } defaults = configure.config.defaults() nose.tools.assert_equal(expect_defaults, defaults) def make_mock_config_parser(name, instance, tracker, defaults=None): """ Make a mock SafeConfigParser object. """ if instance is None: instance = minimock.Mock(name, tracker=tracker) instance.defaults.mock_returns = defaults def mock_parser_read(instance, paths): instance._paths_read = paths return paths instance.read.mock_returns_func = functools.partial( mock_parser_read, instance) return instance def setup_config_parser_fixtures(testcase): """ Set up common ConfigParser fixtures for test cases. """ testcase.mock_tracker = minimock.TraceTracker() testcase.mock_config_defaults = { 'foo': "spam", 'bar': "eggs", } minimock.mock( "configure.config_defaults", mock_obj=testcase.mock_config_defaults) testcase.mock_parser = make_mock_config_parser( name="configure.config", instance=None, defaults=configure.config_defaults, tracker=testcase.mock_tracker) minimock.mock( "ConfigParser.SafeConfigParser", returns_func=functools.partial( make_mock_config_parser, "ConfigParser.SafeConfigParser", testcase.mock_parser, testcase.mock_tracker), tracker=testcase.mock_tracker) minimock.mock( "configure.config", mock_obj=ConfigParser.SafeConfigParser( configure.config_defaults), tracker=testcase.mock_tracker) class read_from_files_TestCase(object): """ Test cases for ‘read_from_files’ function. """ def setup(self): """ Set up test fixtures. """ setup_config_parser_fixtures(self) self.mock_config_file_paths = ["foo.conf", "bar.conf", "baz.conf"] minimock.mock( "configure.config_file_paths", mock_obj=self.mock_config_file_paths) def teardown(self): """ Tear down test fixtures. """ minimock.restore() def test_reads_config_files_to_module_config(self): """ Should read the default config files to module config object. """ expect_file_paths = self.mock_config_file_paths configure.read_from_files() paths_read = self.mock_parser._paths_read nose.tools.assert_equal(expect_file_paths, paths_read) def test_reads_specified_config_files_to_module_config(self): """ Should read the specified config files to module config object. """ test_config_paths = ["wibble", "wobble"] args = { 'paths': test_config_paths, } expect_file_paths = test_config_paths configure.read_from_files(**args) paths_read = self.mock_parser._paths_read nose.tools.assert_equal(expect_file_paths, paths_read) def test_reads_config_files_to_specified_config(self): """ Should read the default config files to specified config. """ test_parser = make_mock_config_parser( "custom_config", instance=None, tracker=self.mock_tracker) args = { 'parser': test_parser, } expect_file_paths = self.mock_config_file_paths configure.read_from_files(**args) paths_read = test_parser._paths_read nose.tools.assert_equal(expect_file_paths, paths_read) def test_reads_specified_config_files_to_specified_config(self): """ Should read the specified config files to specified config. """ test_parser = make_mock_config_parser( "custom_config", instance=None, tracker=self.mock_tracker) test_config_paths = ["wibble", "wobble"] args = { 'parser': test_parser, 'paths': test_config_paths, } expect_file_paths = test_config_paths configure.read_from_files(**args) paths_read = test_parser._paths_read nose.tools.assert_equal(expect_file_paths, paths_read) def setup_config_file_fixtures(testcase): """ Set up common fixtures for config file test cases. """ testcase.mock_config_file = StringIO() def mock_config_open(testcase, filename, mode, buffering): result = testcase.mock_config_file return result testcase.config_open_func = functools.partial( mock_config_open, testcase) testcase.test_config_file_path = tempfile.mktemp() def mock_open(testcase, filename, mode='r', buffering=None): if filename == testcase.test_config_file_path: result = testcase.config_open_func(filename, mode, buffering) else: result = StringIO() return result minimock.mock( "__builtin__.open", returns_func=functools.partial(mock_open, testcase), tracker=testcase.mock_tracker) class read_from_template_TestCase(object): """ Test cases for ‘read_from_template’ function. """ def setup(self): """ Set up test fixtures. """ setup_config_parser_fixtures(self) self.test_parser = make_mock_config_parser( "custom_config", instance=None, tracker=self.mock_tracker) setup_config_file_fixtures(self) def teardown(self): """ Tear down test fixtures. """ minimock.restore() def test_reads_template_to_module_config(self): """ Should read the template config to module config object. """ args = { 'parser': self.test_parser, 'template_path': self.test_config_file_path, } expect_mock_output = textwrap.dedent("""\ ... Called __builtin__.open(%(test_config_file_path)r) Called custom_config.readfp(%(mock_config_file)r) """) % vars(self) configure.read_from_template(**args) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def assert_checker_match(want, got, message=None): """ Assert the wanted output matches the got output. """ checker = doctest.OutputChecker() want = textwrap.dedent(want) source = "" example = doctest.Example(source, want) got = textwrap.dedent(got) checker_optionflags = reduce(operator.or_, [ doctest.ELLIPSIS, ]) if not checker.check_output(want, got, checker_optionflags): if message is None: diff = checker.output_difference( example, got, checker_optionflags) message = "\n".join([ "Output received did not match expected output", "%(diff)s", ]) % vars() raise AssertionError(message) class make_config_from_template_TestCase(object): """ Test cases for ‘make_config_from_template’ function. """ def setup(self): """ Set up test fixtures. """ setup_config_parser_fixtures(self) setup_config_file_fixtures(self) minimock.mock( "configure.read_from_template", tracker=self.mock_tracker) def teardown(self): """ Tear down test fixtures. """ minimock.restore() def test_creates_parser_with_defaults(self): """ Should create a parser with module defaults. """ args = { 'template_path': self.test_config_file_path, } expect_defaults = self.mock_config_defaults parser = configure.make_config_from_template(**args) defaults = parser.defaults() nose.tools.assert_equal(expect_defaults, defaults) def test_populates_parser_from_template_file(self): """ Should populate the parser from specified template file. """ args = { 'template_path': self.test_config_file_path, } expect_mock_output = textwrap.dedent("""\ ... Called configure.read_from_template( %(mock_parser)r, %(test_config_file_path)r) """) % vars(self) configure.make_config_from_template(**args) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_exits_with_error_when_error_from_read_from_template(self): """ When error from ‘read_from_template’, should exit with error. """ scenarios = [ ('OSError', { 'error_instance': OSError(errno.EPERM, "Permission denied"), }), ('IOError', { 'error_instance': IOError(errno.ENOENT, "No such file"), }), ] for (name, scenario) in scenarios: yield ( self.check_exits_with_error, scenario['error_instance']) def check_exits_with_error(self, error_instance): args = { 'template_path': self.test_config_file_path, } configure.read_from_template.mock_raises = error_instance error_message = str(error_instance) expect_exc_class = SystemExit expect_exc_message = textwrap.dedent("""\ ...: %(error_message)s """) % vars() try: configure.make_config_from_template(**args) except expect_exc_class, exc: pass else: raise AssertionError("%(expect_exc_class)r not raised") assert_checker_match(expect_exc_message, str(exc)) class write_to_file_TestCase(object): """ Test cases for ‘write_to_file’ function. """ def setup(self): """ Set up test fixtures. """ setup_config_parser_fixtures(self) setup_config_file_fixtures(self) self.mock_tracker.clear() self.test_args = { 'parser': self.mock_parser, 'path': self.test_config_file_path, } def teardown(self): """ Tear down test fixtures. """ minimock.restore() def test_opens_specified_file_in_write_mode(self): """ Should open specified file in write mode. """ expect_path = self.test_config_file_path expect_mode = 'w' expect_mock_output = textwrap.dedent("""\ Called __builtin__.open( %(expect_path)r, %(expect_mode)r) ... """) % vars() configure.write_to_file(**self.test_args) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_exits_with_error_when_error_from_open(self): """ When error from ‘open’, should exit with error. """ scenarios = [ ('OSError', { 'error_instance': OSError(errno.EPERM, "Permission denied"), }), ('IOError', { 'error_instance': IOError(errno.ENOENT, "No such file"), }), ] for (name, scenario) in scenarios: yield ( self.check_exits_with_error_when_error_from_open, scenario['error_instance']) def check_exits_with_error_when_error_from_open(self, error_instance): __builtin__.open.mock_raises = error_instance error_message = str(error_instance) expect_exc_class = SystemExit expect_exc_message = textwrap.dedent("""\ ...: %(error_message)s """) % vars() try: configure.write_to_file(**self.test_args) except expect_exc_class, exc: pass else: raise AssertionError("%(expect_exc_class)r not raised") assert_checker_match(expect_exc_message, str(exc)) def test_writes_specified_config_to_file(self): """ Should write the specified config to the file. """ expect_file = self.mock_config_file expect_mock_output = textwrap.dedent("""\ ... Called configure.config.write(%(expect_file)r) """) % vars() configure.write_to_file(**self.test_args) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_exits_with_error_when_error_from_config_write(self): """ When error from config ‘write’, should exit with error. """ scenarios = [ ('OSError', { 'error_instance': OSError(errno.EINVAL, "Invalid entry"), }), ('IOError', { 'error_instance': IOError(errno.ENOSPC, "No space left"), }), ] for (name, scenario) in scenarios: yield ( self.check_exits_with_error_when_error_from_config_write, scenario['error_instance']) def check_exits_with_error_when_error_from_config_write( self, error_instance): self.mock_parser.write.mock_raises = error_instance error_message = str(error_instance) expect_exc_class = SystemExit expect_exc_message = textwrap.dedent("""\ ...: %(error_message)s """) % vars() try: configure.write_to_file(**self.test_args) except expect_exc_class, exc: pass else: raise AssertionError("%(expect_exc_class)r not raised") assert_checker_match(expect_exc_message, str(exc)) # Local variables: # mode: python # coding: utf-8 # End: # vim: filetype=python fileencoding=utf-8 : burn-0.4.6/test/test_interactive_configure.py0000664000175000017500000001747611314132111021300 0ustar bignosebignose# -*- coding: utf-8 -*- # test/test_interactive_configure.py # Part of ‘burn’, a tool for writing optical media. # # Copyright © 2009 Ben Finney . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. """ Unit test module for ‘interactive_configure’ module. """ import sys import textwrap import optparse import re import pprint import difflib import nose.tools import minimock from burnlib import interactive_configure import burnlib.version def setup_option_parser_fixtures(testcase): """ Set up common ‘OptionParser’ fixtures for test cases. """ testcase.mock_tracker = minimock.TraceTracker() testcase.test_instance = interactive_configure.OptionParser() minimock.mock( "interactive_configure.OptionParser.error", tracker=testcase.mock_tracker) class OptionParser_TestCase(object): """ Test cases for program ‘OptionParser’ class. """ def setup(self): """ Set up test fixtures. """ test_prog_name = "worple" minimock.mock( "sys.argv", mock_obj=[test_prog_name]) setup_option_parser_fixtures(self) def teardown(self): """ Tear down test fixtures. """ minimock.restore() def test_is_option_parser_instance(self): """ Should be an instance of standard OptionParser class. """ expect_superclass = optparse.OptionParser nose.tools.assert_true( isinstance(self.test_instance, expect_superclass)) def test_has_default_program_name(self): """ Should have default program name. """ expect_result = interactive_configure.OptionParser.default_program_name result = self.test_instance.get_prog_name() nose.tools.assert_equal(expect_result, result) def test_has_default_usage_text(self): """ Should have default usage text. """ expect_usage = ( "Usage: %(default_usage)s\n" % vars(interactive_configure.OptionParser)) program_name = interactive_configure.OptionParser.default_program_name expect_result = re.sub('%prog', program_name, expect_usage) result = self.test_instance.get_usage() nose.tools.assert_equal(expect_result, result) def test_has_expected_version(self): """ Should have expected version string. """ expect_result = burnlib.version.version result = self.test_instance.get_version() nose.tools.assert_equal(expect_result, result) def test_has_default_description(self): """ Should have default summary description. """ expect_result = interactive_configure.OptionParser.default_description result = self.test_instance.get_description() nose.tools.assert_equal(expect_result, result) def test_has_default_epilog(self): """ Should have default help epilogue text. """ expect_result = interactive_configure.OptionParser.default_epilog result = self.test_instance.epilog nose.tools.assert_equal(expect_result, result) def check_has_expected_option(self, expect_option): def get_option_attrs(option): attrs = dict( (name, getattr(option, name)) for name in [ '_short_opts', '_long_opts', 'action', 'type', 'dest', 'default', 'nargs', 'const', 'choices', 'metavar']) return attrs expect_option_attrs = get_option_attrs(expect_option) option_string = expect_option._long_opts[0] nose.tools.assert_true( self.test_instance.has_option(option_string), "option not found: %(option_string)r" % vars()) option = self.test_instance.get_option(option_string) option_attrs = get_option_attrs(option) nose.tools.assert_equal( expect_option_attrs, option_attrs, "\n".join(difflib.unified_diff( pprint.saferepr(expect_option_attrs).split(), pprint.saferepr(option_attrs).split())) ) def test_has_expected_options(self): """ Should have expected options. """ options = [ optparse.Option( "-t", "--template-file", action='store', type='string', dest='template_file_path', default="/usr/share/burn/example/burn.conf", metavar="PATH"), optparse.Option( "-o", "--output-file", action='store', type='string', dest='output_file_path', default="burn.conf.new", metavar="PATH"), ] for option in options: yield ( self.check_has_expected_option, option) class OptionParser_check_values_TestCase(object): """ Test cases for ‘OptionParser.check_values’ method. """ mock_option_values = minimock.Mock("Values") valid_scenarios = [ ('no args', { 'in_params': {'values': mock_option_values, 'args': []}, }), ] invalid_scenarios = [ ('one arg', { 'in_params': {'values': mock_option_values, 'args': ["foo"]}, 'message': "unexpected arguments: foo", }), ('two args', { 'in_params': {'values': mock_option_values, 'args': ["foo", "bar"]}, 'message': "unexpected arguments: foo bar", }), ] def setup(self): """ Set up test fixtures. """ setup_option_parser_fixtures(self) for (name, scenario) in self.valid_scenarios: scenario['expect_result'] = ( scenario['in_params']['values'], scenario['in_params']['args']) def teardown(self): """ Tear down test fixtures. """ minimock.restore() def test_requests_error_when_invalid_arg_count(self): """ Should request parser error when invalid argument count. """ for (name, scenario) in self.invalid_scenarios: yield ( self.check_requests_error_when_invalid_arg_count, scenario) def check_requests_error_when_invalid_arg_count(self, scenario): in_params = scenario['in_params'] expect_error_message = scenario['message'] expect_mock_output = textwrap.dedent("""\ ...Called interactive_configure.OptionParser.error(%(expect_error_message)r) """) % vars() self.test_instance.check_values(**in_params) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_returns_expected_options_and_args(self): """ Should return expected ‘options’ and ‘args’. """ for (name, scenario) in self.valid_scenarios: yield ( self.check_returns_expected_options_and_args, scenario) def check_returns_expected_options_and_args(self, scenario): in_params = scenario['in_params'] expect_result = scenario['expect_result'] result = self.test_instance.check_values(**in_params) nose.tools.assert_equal(expect_result, result) # Local variables: # mode: python # coding: utf-8 # End: # vim: filetype=python fileencoding=utf-8 : burn-0.4.6/test/test_console.py0000644000175000017500000010100411237733167016365 0ustar bignosebignose# -*- coding: utf-8 -*- # test/test_console.py # Part of ‘burn’, a tool for writing optical media. # # Copyright © 2009 Ben Finney . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. """ Unit test module for ‘console’ module. """ import __builtin__ import sys import os import textwrap import errno import tty import termios import nose import nose.tools import minimock from burnlib import console def setup_ask_value_fixtures(testcase): """ Set up test fixtures common to ask_value test cases. """ testcase.mock_tracker = minimock.TraceTracker() testcase.test_response = str(object()) minimock.mock( "__builtin__.raw_input", returns=testcase.test_response, tracker=testcase.mock_tracker) minimock.mock( "sys.stderr", tracker=testcase.mock_tracker) class ask_value_TestCase(object): """ Test cases for ask_value function. """ def setup(self): """ Set up test fixtures. """ setup_ask_value_fixtures(self) def teardown(self): """ Test down test fixtures. """ minimock.restore() def test_gets_input_from_console(self): """ Should get input from console via ‘raw_input’. """ test_prompt = "foo" expect_mock_output = textwrap.dedent("""\ Called __builtin__.raw_input(...) """) % vars() console.ask_value(test_prompt) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_prompt_has_consistent_ending(self): """ Should append consistent ending to prompt string. """ test_prompt = "foo" expect_prompt = "%(test_prompt)s? " % vars() expect_mock_output = textwrap.dedent("""\ Called __builtin__.raw_input(%(expect_prompt)r) """) % vars() console.ask_value(test_prompt) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_returns_expected_response(self): """ Should return expected response from standard input. """ test_prompt = "foo" expect_response = self.test_response response = console.ask_value(test_prompt) nose.tools.assert_equal(expect_response, response) class ask_value_default_TestCase(object): """ Test cases for ask_value function, with default. """ def setup(self): """ Set up test fixtures. """ setup_ask_value_fixtures(self) def teardown(self): """ Test down test fixtures. """ minimock.restore() def test_prompt_includes_default(self): """ Should prompt including default value. """ test_prompt = "foo" test_default = str(object()) expect_prompt = "%(test_prompt)s [%(test_default)s]? " % vars() expect_mock_output = textwrap.dedent("""\ Called __builtin__.raw_input(%(expect_prompt)r) """) % vars() console.ask_value(test_prompt, test_default) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_returns_default_if_empty_response(self): """ Should return default value if empty response. """ test_prompt = "foo" test_default = str(object()) __builtin__.raw_input.mock_returns = "" expect_response = test_default response = console.ask_value(test_prompt, test_default) nose.tools.assert_equal(expect_response, response) class make_boolean_response_TestCase(object): """ Test cases for make_boolean_response function. """ def test_returns_expected_result(self): """ Should return expected result for specified in_text value.""" scenarios = [ ('n', {'in_text': "n", 'result': False}), ('N', {'in_text': "N", 'result': False}), ('no', {'in_text': "no", 'result': False}), ('No', {'in_text': "No", 'result': False}), ('NO', {'in_text': "NO", 'result': False}), ('nO', {'in_text': "nO", 'result': False}), ('y', {'in_text': "y", 'result': True}), ('Y', {'in_text': "Y", 'result': True}), ('ye', {'in_text': "ye", 'result': True}), ('Ye', {'in_text': "Ye", 'result': True}), ('YE', {'in_text': "YE", 'result': True}), ('yes', {'in_text': "yes", 'result': True}), ('Yes', {'in_text': "Yes", 'result': True}), ('YES', {'in_text': "YES", 'result': True}), ] for (name, scenario) in scenarios: test_case_args = [ self.check_result_matches, scenario['result'], scenario['in_text']] yield tuple(test_case_args) def check_result_matches(self, expect_result, in_text, default=None): args = [in_text] result = console.make_boolean_response(*args) nose.tools.assert_equal(expect_result, result) def test_invalid_input_raises_error(self): """ Should raise ValueError for invalid in_text. """ scenarios = [ ('empty', {'in_text': ""}), ('bogus', {'in_text': "bogus"}), ('ni', {'in_text': "ni"}), ('nope', {'in_text': "nope"}), ('NOPE', {'in_text': "nope"}), ('Yarr', {'in_text': "Yarr"}), ('yoghurt', {'in_text': "yoghurt"}), ('yesterday', {'in_text': "yesterday"}), ] expect_error = ValueError for (name, scenario) in scenarios: yield ( self.check_raises_expected_error, expect_error, scenario['in_text']) def check_raises_expected_error(self, expect_error, in_text): nose.tools.assert_raises( expect_error, console.make_boolean_response, in_text) def test_empty_input_raises_error(self): """ Should raise ValueError for empty input. """ in_text = "" expect_error = ValueError self.check_raises_expected_error(expect_error, in_text) class make_yesno_response_TestCase(object): """ Test cases for make_yesno_response function. """ def test_returns_expected_result(self): """ Should return expected result for specified in_text value.""" scenarios = [ ('false', {'in_value': False, 'result': "no"}), ('true', {'in_value': True, 'result': "yes"}), ] for (name, scenario) in scenarios: test_case_args = [ self.check_result_matches, scenario['result'], scenario['in_value']] yield tuple(test_case_args) def check_result_matches(self, expect_result, in_text, default=None): args = [in_text] result = console.make_yesno_response(*args) nose.tools.assert_equal(expect_result, result) class get_yesno_response_TestCase(object): """ Test cases for get_yesno_response function. """ def setup(self): """ Set up test fixtures. """ self.mock_tracker = minimock.TraceTracker() minimock.mock( "console.ask_value", returns="yes", tracker=self.mock_tracker) minimock.mock( "sys.stderr", tracker=self.mock_tracker) minimock.mock( "console.make_boolean_response", returns=True, tracker=self.mock_tracker) def teardown(self): """ Test down test fixtures. """ minimock.restore() def test_gets_input_from_ask_value(self): """ Should get input from console via ‘ask_value’. """ test_prompt = "foo" expect_mock_output = textwrap.dedent("""\ Called console.ask_value(...) ... """) % vars() console.get_yesno_response(test_prompt) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_prompt_includes_yesno_specifier(self): """ Should append yes/no specifier to prompt string. """ test_prompt = "foo" expect_prompt = "%(test_prompt)s (yes/no)" % vars() expect_mock_output = textwrap.dedent("""\ Called console.ask_value(%(expect_prompt)r, None) ... """) % vars() console.get_yesno_response(test_prompt) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_passes_expected_args_to_ask_value(self): """ Should pass expected arguments to ‘ask_value’. """ test_prompt = "foo" test_ask_value_prompt_template = "%(test_prompt)s (yes/no)" scenarios = [ ('no default', { 'in_args': dict( prompt="foo"), 'call_args': ["foo (yes/no)", None], }), ('default true', { 'in_args': dict( prompt="bar", default=True), 'call_args': ["bar (yes/no)", "yes"], }), ('no default, spam complaint', { 'in_args': dict( prompt="baz", complaint="Spam"), 'call_args': ["baz (yes/no)", None], }), ('default false, spam complaint', { 'in_args': dict( prompt="baz", default=False, complaint="Spam"), 'call_args': ["baz (yes/no)", "no"], }), ] for (name, scenario) in scenarios: yield ( self.check_args_passed_to_function, "console.ask_value", scenario['in_args'], scenario['call_args']) def test_passes_expected_args_to_make_boolean_response(self): """ Should pass expected arguments to ‘make_boolean_response’. """ test_response = "b0gUs" scenarios = [ ('no default', { 'in_args': dict( prompt="foo"), 'call_args': [test_response], }), ('default true', { 'in_args': dict( prompt="bar", default=True), 'call_args': [test_response], }), ('no default, spam complaint', { 'in_args': dict( prompt="baz", complaint="Spam"), 'call_args': [test_response], }), ('default false, spam complaint', { 'in_args': dict( prompt="baz", default=False, complaint="Spam"), 'call_args': [test_response], }), ] for (name, scenario) in scenarios: yield ( self.check_args_passed_to_function, "console.make_boolean_response", scenario['in_args'], scenario['call_args']) def check_args_passed_to_function(self, func_name, in_args, call_args): console.ask_value.mock_returns = call_args[0] console.make_boolean_response.mock_returns = True expect_call_args = ", ".join(repr(arg) for arg in call_args) expect_mock_output = textwrap.dedent("""\ ...Called %(func_name)s(%(expect_call_args)s)... """) % vars() console.get_yesno_response(**in_args) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_returns_expected_result(self): """ Should return expected boolean result. """ scenarios = [ ('true', {'bool_response': True}), ('false', {'bool_response': False}), ] for (name, scenario) in scenarios: expect_result = scenario['bool_response'] yield ( self.check_result_matches, expect_result, scenario['bool_response']) def check_result_matches(self, expect_result, bool_response): console.make_boolean_response.mock_returns = bool_response test_prompt = "foo" result = console.get_yesno_response(test_prompt) nose.tools.assert_equal(expect_result, result) def test_displays_complaint_when_invalid_input_received(self): """ When invalid input, should display specified complaint. """ expect_error = test_error = ValueError console.make_boolean_response.mock_raises = test_error test_prompt = "Foo prompt" test_complaint = "Bar complaint" in_args = dict( prompt=test_prompt, complaint=test_complaint, ) expect_complaint_output = "%(test_complaint)s\n" % vars() expect_mock_output = textwrap.dedent("""\ ... Called sys.stderr.write(%(expect_complaint_output)r) """) % vars() try: console.get_yesno_response(**in_args) except expect_error: pass nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_displays_nothing_when_valid_input_received(self): """ When valid input, should display no complaint. """ test_prompt = "Foo prompt" test_complaint = "Bar complaint" in_args = dict( prompt=test_prompt, complaint=test_complaint, ) unwanted_complaint_output = "%(test_complaint)s\n" % vars() unwanted_mock_output = textwrap.dedent("""\ ... Called sys.stderr.write(%(unwanted_complaint_output)r) """) % vars() console.get_yesno_response(**in_args) nose.tools.assert_false( self.mock_tracker.check(unwanted_mock_output), self.mock_tracker.dump()) def test_displays_nothing_when_invalid_input_no_complaint(self): """ When invalid input but no specified complaint, no display. """ expect_error = test_error = ValueError console.make_boolean_response.mock_raises = test_error test_prompt = "Foo prompt" in_args = dict( prompt=test_prompt, ) unwanted_mock_output = textwrap.dedent("""\ ... Called sys.stderr.write(...) """) % vars() try: console.get_yesno_response(**in_args) except expect_error: pass nose.tools.assert_false( self.mock_tracker.check(unwanted_mock_output), self.mock_tracker.dump()) def test_propagates_response_conversion_error(self): """ Should propagate ValueError from make_boolean_response. """ test_prompt = "foo" expect_error = test_error = ValueError console.make_boolean_response.mock_raises = test_error nose.tools.assert_raises( expect_error, console.get_yesno_response, test_prompt) class ask_yesno_TestCase(object): """ Test cases for ask_yesno function. """ def setup(self): """ Set up test fixtures. """ self.mock_tracker = minimock.TraceTracker() minimock.mock( "console.get_yesno_response", returns=True, tracker=self.mock_tracker) def teardown(self): """ Test down test fixtures. """ minimock.restore() def test_calls_get_yesno_response_with_expected_args(self): """ Should call get_yesno_response with expected arguments. """ default_complaint = "Please answer [y]es or [n]o." scenarios = [ ('no default', { 'in_args': dict( prompt="foo"), 'call_args': ["foo", None, default_complaint], }), ('default True', { 'in_args': dict( prompt="foo", default=True), 'call_args': ["foo", True, default_complaint], }), ('no default, spam complaint', { 'in_args': dict( prompt="foo", complaint="Spam"), 'call_args': ["foo", None, "Spam"], }), ('default false, spam complaint', { 'in_args': dict( prompt="foo", default=False, complaint="Spam"), 'call_args': ["foo", False, "Spam"], }), ] for (name, scenario) in scenarios: yield ( self.check_calls_function_with_expected_arguments, "console.get_yesno_response", scenario['in_args'], scenario['call_args']) def check_calls_function_with_expected_arguments( self, func_name, in_args, call_args): expect_call_args = ", ".join((repr(arg) for arg in call_args)) expect_mock_output = ( "Called %(func_name)s(%(expect_call_args)s)" ) % vars() console.ask_yesno(**in_args) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def make_mock_get_yesno_response(self, response_sequence): """ Make a mock ‘get_yesno_response’ function returning responses. """ def mock_get_yesno_response(*args): response = response_sequence.pop(0) if isinstance(response, Exception): raise response return response return mock_get_yesno_response def test_calls_get_yesno_response_until_valid_response(self): """ Should call get_yesno_response repeatedly until valid. Whenever get_yesno_response raises a ValueError, ask_yesno should loop and call it again until a valid response is received. """ response_sequence = [ValueError("Bad input")] * 5 + [True] console.get_yesno_response.mock_returns = None console.get_yesno_response.mock_returns_func = ( self.make_mock_get_yesno_response(response_sequence)) in_args = ["foo"] expect_mock_output = ( "Called console.get_yesno_response(...)\n" * len(response_sequence)) console.ask_yesno(*in_args) nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_returns_expected_result(self): """ Should return expected boolean result. """ scenarios = [ ('true', {'bool_response': True}), ('false', {'bool_response': False}), ] for (name, scenario) in scenarios: expect_result = scenario['bool_response'] yield ( self.check_result_matches, expect_result, scenario['bool_response']) def check_result_matches(self, expect_result, bool_response): console.get_yesno_response.mock_returns = bool_response test_prompt = "foo" result = console.ask_yesno(test_prompt) nose.tools.assert_equal(expect_result, result) class getch_TestCase(object): """ Test cases for getch function. """ def setup(self): """ Set up test fixtures. """ self.mock_tracker = minimock.TraceTracker() minimock.mock( "sys.stdin", tracker=self.mock_tracker) self.mock_stdin_fd = 42 sys.stdin.fileno.mock_returns = self.mock_stdin_fd self.mock_stdin_char = "Θ" mock_stdin_stream = [self.mock_stdin_char] def mock_stdin_read(size=-1): if size < 0: size = len(mock_stdin_stream) result = "".join(mock_stdin_stream.pop(0) for i in range(size)) return result minimock.mock( "sys.stdin.read", returns_func=mock_stdin_read, tracker=self.mock_tracker) self.mock_orig_term_attrs = object() self.mock_term_attrs = self.mock_orig_term_attrs def mock_tcgetattr(fd): if fd == self.mock_stdin_fd: return self.mock_term_attrs else: raise termios.error(errno.EBADF, "Bad file descriptor") minimock.mock( "termios.tcgetattr", returns_func=mock_tcgetattr, tracker=self.mock_tracker) minimock.mock( "termios.tcsetattr", tracker=self.mock_tracker) minimock.mock( "termios.TCSADRAIN") def mock_tty_setraw(fd, when=None): self.mock_term_attrs = object() minimock.mock( "tty.setraw", returns_func=mock_tty_setraw, tracker=self.mock_tracker) def teardown(self): """ Tear down test fixtures. """ minimock.restore() def test_sets_stdin_to_unbuffered_mode(self): """ Should set stdin terminal to unbuffered (raw) mode. """ expect_mock_output = textwrap.dedent("""\ ... Called tty.setraw(%(mock_stdin_fd)r) ... """) % vars(self) console.getch() nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_reads_one_char_from_stdin(self): """ Should read exactly one character from stdin terminal. """ expect_mock_output = textwrap.dedent("""\ ... Called sys.stdin.read(1) ... """) % vars(self) console.getch() nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_returns_one_char_from_stdin(self): """ Should read exactly one character from stdin terminal. """ expect_result = self.mock_stdin_char result = console.getch() nose.tools.assert_equal(expect_result, result) def test_propagates_error(self): """ Should propagate error from specified function. """ scenarios = [ ('tty.setraw error', { 'error': OSError(), 'func_name': "tty.setraw"}), ('sys.stdin.read error', { 'error': IOError(), 'func_name': "sys.stdin.read"}), ] for (name, scenario) in scenarios: yield ( self.check_propagates_error, scenario['error'], scenario['func_name']) def check_propagates_error(self, test_error, error_func_name): minimock.mock( error_func_name, raises=test_error) expect_error = test_error nose.tools.assert_raises( type(expect_error), console.getch) def test_restores_term_settings(self): """ On error or not, should reset stdin terminal settings. """ scenarios = [ ('no error', {'error': None}), ('tty.setraw error', { 'error': OSError(), 'func_name': "tty.setraw"}), ('sys.stdin.read error', { 'error': IOError(), 'func_name': "sys.stdin.read"}), ] for (name, scenario) in scenarios: yield ( self.check_restores_term_settings, scenario['error'], scenario.get('func_name')) def check_restores_term_settings(self, test_error, error_func_name): if test_error: minimock.mock( error_func_name, raises=test_error) expect_fd = self.mock_stdin_fd expect_when = termios.TCSADRAIN expect_attrs = self.mock_orig_term_attrs expect_mock_output = textwrap.dedent("""\ ... Called termios.tcsetattr( %(expect_fd)r, %(expect_when)r, %(expect_attrs)r) """) % vars() try: console.getch() except type(test_error): pass nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) progressbar_scenarios = { '12 wide': { 'args': [12], 'width': 12, 'max_amount': 100, }, '30 wide': { 'args': [30], 'width': 30, 'max_amount': 100, 'amount': 12, }, } class ProgressBar_TestCase(object): """ Test cases for ProgressBar class. """ def setup(self): """ Set up test fixtures. """ for (name, scenario) in progressbar_scenarios.items(): instance = console.ProgressBar(*scenario['args']) scenario['instance'] = instance def teardown(self): """ Tear down test fixtures. """ minimock.restore() def test_has_specified_width(self): """ Should have specified width. """ attribute_name = 'width' for (name, scenario) in progressbar_scenarios.items(): yield ( self.check_has_specified_attribute_value, name, attribute_name, scenario[attribute_name]) def test_has_specified_max_amount(self): """ Should have specified max_amount. """ attribute_name = 'max_amount' for (name, scenario) in progressbar_scenarios.items(): yield ( self.check_has_specified_attribute_value, name, attribute_name, scenario[attribute_name]) def test_has_zero_amount(self): """ Should have initial amount of 0. """ attribute_name = 'amount' for (name, scenario) in progressbar_scenarios.items(): yield ( self.check_has_specified_attribute_value, name, attribute_name, 0) def check_has_specified_attribute_value( self, scenario_name, attribute_name, expect_value): instance = progressbar_scenarios[scenario_name]['instance'] attribute_value = getattr(instance, attribute_name) nose.tools.assert_equal(expect_value, attribute_value) def test_set_amount_result_is_as_expected(self): """ Should set amount to expected value. """ test_instance = console.ProgressBar(width=10, max_amount=100) scenarios = [ ('min 0, set 1', { 'set_amount': 1, 'expect_amount': 1, }), ('min 0, set 0', { 'set_amount': 0, 'expect_amount': 0, }), ('min 0, set -1', { 'set_amount': -1, 'expect_amount': 0, }), ('max 100, set 99', { 'set_amount': 99, 'expect_amount': 99, }), ('max 100, set 100', { 'set_amount': 100, 'expect_amount': 100, }), ('max 100, set 101', { 'set_amount': 101, 'expect_amount': 100, }), ] for (name, scenario) in scenarios: scenario['instance'] = test_instance yield ( self.check_set_amount_result_is_as_expected, scenario['expect_amount'], scenario['instance'], scenario['set_amount']) def check_set_amount_result_is_as_expected( self, expect_amount, instance, set_amount): instance.amount = set_amount nose.tools.assert_equal(expect_amount, instance.amount) def test_string_is_as_expected(self): """ Should have expected string representation. """ scenarios = [ ('width 12, amount 0', { 'args': {'width': 12}, 'amount': 0, 'expect_string': "[ 0% ]", }), ('width 12, max 8, amount 1', { 'args': {'width': 12, 'max_amount': 8}, 'amount': 1, 'expect_string': "[= 13% ]", }), ('width 32, amount 0', { 'args': {'width': 32}, 'amount': 0, 'expect_string': "[ 0% ]", }), ('width 32, amount 6', { 'args': {'width': 32}, 'amount': 6, 'expect_string': "[= 6% ]", }), ('width 32, amount 7', { 'args': {'width': 32}, 'amount': 7, 'expect_string': "[== 7% ]", }), ('width 32, amount 10', { 'args': {'width': 32}, 'amount': 10, 'expect_string': "[=== 10% ]", }), ('width 32, amount 45', { 'args': {'width': 32}, 'amount': 45, 'expect_string': "[=============45% ]", }), ('width 32, amount 56', { 'args': {'width': 32}, 'amount': 56, 'expect_string': "[=============56% ]", }), ('width 32, amount 57', { 'args': {'width': 32}, 'amount': 57, 'expect_string': "[=============57%= ]", }), ('width 32, amount 99', { 'args': {'width': 32}, 'amount': 99, 'expect_string': "[=============99%============= ]", }), ('width 32, amount 100', { 'args': {'width': 32}, 'amount': 100, 'expect_string': "[=============100%=============]", }), ] for (name, scenario) in scenarios: scenario['instance'] = console.ProgressBar(**scenario['args']) yield ( self.check_string_is_as_expected, scenario['expect_string'], scenario['instance'], scenario['amount']) def check_string_is_as_expected(self, expect_string, instance, amount): instance.amount = amount string_result = str(instance) nose.tools.assert_equal(expect_string, string_result) class ProgressBar_update_display_TestCase(object): """ Test cases for ProgressBar.update_display method. """ def setup(self): """ Set up test fixtures. """ self.mock_tracker = minimock.TraceTracker() self.mock_bar_text = str(object()) minimock.mock( "console.ProgressBar.__str__", returns=self.mock_bar_text, tracker=self.mock_tracker) minimock.mock( "sys.stderr", tracker=self.mock_tracker) self.test_instance = console.ProgressBar(width=12) def teardown(self): """ Tear down test fixtures. """ minimock.restore() def test_writes_to_stderr(self): """ Should write to stderr. """ instance = self.test_instance expect_mock_output = textwrap.dedent("""\ ... Called sys.stderr.write(...) ... """) instance.update_display() nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_writes_string_with_carriage_return(self): """ Should write progress bar string with carriage return. """ instance = self.test_instance expect_stderr_output = "%(mock_bar_text)s\r" % vars(self) expect_mock_output = textwrap.dedent("""\ ... Called sys.stderr.write(%(expect_stderr_output)r) ... """ % vars()) instance.update_display() nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def test_finally_flushes_stderr_buffer(self): """ Should finally flush the stderr buffer. """ instance = self.test_instance expect_mock_output = textwrap.dedent("""\ ... Called sys.stderr.flush() """) instance.update_display() nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) # Local variables: # mode: python # coding: utf-8 # End: # vim: filetype=python fileencoding=utf-8 : burn-0.4.6/test/test_audio.py0000644000175000017500000000447311314132111016012 0ustar bignosebignose# -*- coding: utf-8 -*- # test/test_audio.py # Part of ‘burn’, a tool for writing optical media. # # Copyright © 2009 Ben Finney . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. """ Unit test module for ‘audio’ module. """ import subprocess import textwrap import nose.tools import minimock from burnlib import audio def test_compute_duration_returns_expected_output(): """ Should return expected output for specified seconds value. """ scenarios = [ ('0:00', {'seconds': 0, 'result': "0:00"}), ('0:01', {'seconds': 1, 'result': "0:01"}), ('0:07', {'seconds': 7, 'result': "0:07"}), ('0:20', {'seconds': 20, 'result': "0:20"}), ('0:58', {'seconds': 58, 'result': "0:58"}), ('0:59', {'seconds': 59, 'result': "0:59"}), ('1:00', {'seconds': 60, 'result': "1:00"}), ('1:01', {'seconds': 61, 'result': "1:01"}), ('1:42', {'seconds': 102, 'result': "1:42"}), ('27:19', {'seconds': 1639, 'result': "27:19"}), ('59:59', {'seconds': 3599, 'result': "59:59"}), ('1:00:00', {'seconds': 3600, 'result': "1:00:00"}), ('1:00:01', {'seconds': 3601, 'result': "1:00:01"}), ('7:05:36', {'seconds': 25536, 'result': "7:05:36"}), ] for (name, scenario) in scenarios: yield ( check_compute_duration_output, scenario['seconds'], scenario['result']) def check_compute_duration_output(seconds, expect_result): result = audio.compute_duration(seconds) nose.tools.assert_equal(expect_result, result) # Local variables: # mode: python # coding: utf-8 # End: # vim: filetype=python fileencoding=utf-8 : burn-0.4.6/test/test_device.py0000664000175000017500000001044711314132111016150 0ustar bignosebignose# -*- coding: utf-8 -*- # test/test_device.py # Part of ‘burn’, a tool for writing optical media. # # Copyright © 2009 Ben Finney . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. """ Unit test module for ‘device’ module. """ import os import subprocess import textwrap import nose.tools import minimock from burnlib import device def setup_popen_fixtures(testcase): """ Set up common fixtures for ‘subprocess.Popen’ test cases. """ testcase.mock_tracker = minimock.TraceTracker() testcase.mock_popen = minimock.Mock( "Popen", tracker=testcase.mock_tracker) testcase.mock_popen.communicate.mock_returns = (None, None) minimock.mock( "subprocess.Popen", returns=testcase.mock_popen, tracker=testcase.mock_tracker) class wodim_query_TestCase(object): """ Test cases for functions that query ‘wodim’ command. """ test_func = NotImplemented def setup(self): """ Set up test fixtures. """ setup_popen_fixtures(self) def teardown(self): """ Tear down test fixtures. """ minimock.restore() def check_creates_expected_popen(self): """ Should create a Popen instance with expected arguments. """ expect_command_args = self.command_args expect_popen_stdout = subprocess.PIPE expect_mock_output = textwrap.dedent("""\ Called subprocess.Popen( %(expect_command_args)r, stdout=%(expect_popen_stdout)r ) ... """) % vars() func = self.__class__.test_func func() nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def check_communicates_with_popen_pipe(self): """ Should communicate with the Popen instance's pipe. """ expect_mock_output = textwrap.dedent("""\ ... Called Popen.communicate() """) % vars() func = self.__class__.test_func func() nose.tools.assert_true( self.mock_tracker.check(expect_mock_output), self.mock_tracker.diff(expect_mock_output)) def check_returns_popen_pipe_output(self): """ Should return the output from the Popen instance's stdout. """ test_output = "foo\nbar" self.mock_popen.communicate.mock_returns = (test_output, None) expect_output = test_output func = self.__class__.test_func result = func() nose.tools.assert_equal(expect_output, result) class device_list_output_TestCase(wodim_query_TestCase): """ Test cases for ‘device_list_output’ function. """ test_func = staticmethod(device.device_list_output) command_args = ["wodim", "-devices"] def test_creates_expected_popen(self): self.check_creates_expected_popen() def test_communicates_with_popen_pipe(self): self.check_communicates_with_popen_pipe() def test_returns_popen_pipe_output(self): self.check_returns_popen_pipe_output() class bus_list_output_TestCase(wodim_query_TestCase): """ Test cases for ‘bus_list_output’ function. """ test_func = staticmethod(device.bus_list_output) command_args = ["wodim", "-scanbus"] def test_creates_expected_popen(self): self.check_creates_expected_popen() def test_communicates_with_popen_pipe(self): self.check_communicates_with_popen_pipe() def test_returns_popen_pipe_output(self): self.check_returns_popen_pipe_output() # Local variables: # mode: python # coding: utf-8 # End: # vim: filetype=python fileencoding=utf-8 : burn-0.4.6/test/__init__.py0000644000175000017500000000057111237733167015432 0ustar bignosebignose# -*- coding: utf-8 -*- # # tests/__init__.py # # Copyright © 2008–2009 Ben Finney # This is free software; you may copy, modify and/or distribute this work # under the terms of the GNU General Public License, version 2 or later. # No warranty expressed or implied. See the file LICENSE for details. """ Unit test suite for ‘burn’. """ burn-0.4.6/doc/0000755000175000017500000000000011314307442013071 5ustar bignosebignoseburn-0.4.6/doc/docutils.conf0000644000175000017500000000016711237733167015605 0ustar bignosebignose# doc/docutils.conf # Docutils configuration for Burn distribution. [html4css1 writer] stylesheet-path = doc/burn.css burn-0.4.6/doc/KNOWN_BUGS0000664000175000017500000000057211222766143014543 0ustar bignosebignoseThis is not a bug: even if the given audio files order is different, wav files will be processed and burned before ogg files which will be processed and burned before mp3 files. So no order will be kept. Internal decoding (mp3 to wav) in powerpc does not work and ends up in noisy audio files. If you have powerpc use external_decoding=yes in burn.conf burn-0.4.6/doc/burn.1.txt0000664000175000017500000000750511237733167014763 0ustar bignosebignose==== burn ==== ------------------------------------------------------ record from various sources to optical media (CD, DVD) ------------------------------------------------------ :Author: |author| :Date: 2009-07-22 :Copyright: This is free software: you may copy, modify, and/or distribute this work under the terms of the GNU General Public License, version 2 or later as published by the Free Software Foundation. No warranty expressed or implied. See the source for details. :Manual section: 1 :Manual group: Burn .. |command| replace:: burn SYNOPSIS ======== | |command| `MAIN_MODE` `[OPTIONS]...` `[FILE/S]...` | |command| --help DESCRIPTION =========== CD-writing program/script. Features Data-CD, Audio-CD, Copy on the fly, Iso-CD. CONFIGURATION ============= Configuration files for burn are: ``~/.burnrc`` or ``/etc/burn.conf``. You can edit them to change values. If you don't have these files you can: 1) use ``burn-configure`` utility 2) take the configuration template, edit and copy it as ``~/.burnrc`` or ``/etc/burn.conf`` MAIN MODES ========== -D, --data-cd To create Data-CD -I, --iso-cd To create ISO-CD -C, --copy-cd To copy CDs -A, --audio-cd To create an audio CD from WAV, MP3 and ogg files EXAMPLES ======== :: # burn -D -p /etc/ Creates a CD with /etc/ contents. (you will find files and directories contained in /etc in CD's root.) :: # burn -D -p /home/bigpaul/video/summer_2003/spain.tar.gz Creates a CD with spain.tar.gz in CD's root :: # burn -D -r /etc/ Creates a CD containing the whole /etc/ directory. (-r preserves path) :: # burn -D -c /mail_2003 /home/bigpaul/Mail -p /boot/vmli* Creates a CD containing the whole /home/bigpaul/Mail renamed into /mail_2003. (-c changes path name). This command also adds in CD's root every vmli* file in /boot/ directory :: # burn -I -n image.iso Burns image.iso :: # burn -C Copy CDs (disk at once). :: # burn -A -a *.wav Creates an Audio CD. Tracks come from wav files :: # burn -A -a *.mp3 Creates an Audio CD. Tracks come from mp3 files :: # burn -A -a *.ogg Creates an Audio CD. Tracks come from Ogg Vorbis files :: # burn -A -a *.mp3 file.ogg track01.wav Creates an Audio CD preserving input order. In this example the first audio tracks will come from mp3 files, than we will find the one from ``file.ogg`` and finally the one from ``track01.wav`` GENERAL OPTIONS =============== -s, --simulate to perform a burn simulation DATA-CD OPTIONS =============== -p, --path add file/s or path's content to CD-ROM's root. e.g.: ``-p /cvs/myproj/``. In this example we will find CD-ROM's root filled with ``/cvs/myproj/`` contents, but no ``/cvs/myproj/`` will be created -r, --preserve-path add file/s or path's content to CD-ROM preserving original path. e.g.: ``-r /cvs/myproj/``. In this example we will find ``/cvs/myproj/`` in CD-ROM's root -x, --exclude-path every file or directory matching this string will not be included -c, --change-path old_path will be named new_path in CD-ROM. e.g.: ``-c /my_home/2004_Jan/ /home/bigpaul/``. Thus ``/home/bigpaul/`` will be named ``/my_home/2004_Jan/`` in CD-ROM. -l, --follow-symlink this option allows burn to follow symbolic link directories -m, --multisession this option allows multisession CDs ISO-CD OPTIONS ============== -n, --name image name AUDIO-CD OPTIONS ================ -a, --audio-file mp3, ogg or wav file/s .. |author| replace:: Ben Finney , Gaetano Paolone .. Local variables: mode: rst coding: utf-8 time-stamp-format: "%:y-%02m-%02d" time-stamp-start: "^:Date:[ ]+" time-stamp-end: "$" time-stamp-line-limit: 20 End: vim: filetype=rst fileencoding=utf-8 : burn-0.4.6/doc/burn.css0000644000175000017500000000124611237733167014567 0ustar bignosebignose/* doc/burn.css * Cascading Style Sheet for Burn project documentation. */ body { font-family: verdana, helvetica, sans; margin-left: 80px; width: 60%; text-align: justify; border-left: 1px solid gray; padding-left: 20px; background-image: url(burn-flame.jpeg); background-repeat: no-repeat; background-position: top left; background-attachment: fixed; } p { margin-left: 20px; } div.section h1, div.section h2 { color: #249; } h1.title { text-align: left; border-bottom: 1px dotted gray; } p.rubric { text-align: right; text-decoration: underline; font-weight: bold; color: gray; } burn-0.4.6/doc/WISHLIST0000664000175000017500000000007411222766143014272 0ustar bignosebignose- tracks optimization - files with compression/delete rules burn-0.4.6/doc/BUGS0000664000175000017500000000103611222766143013563 0ustar bignosebignosedatacd - -n newpath = oldpath If second argument is not specified, no error is raised. CLOSED -A -s -a (-s option must be after MODE) I closed it even better: CLOSED it checks media capacity before disk usage. So it asks to go on even if there is no disk space. CLOSED spaces in mp3 and ogg files bring to decoder problems. It seems due to an incorrect shell expansion. Decoder tries to decode filename's splitted portions. CLOSED spaces in mp3 and ogg files bring to delete from temp directory problems. burn-0.4.6/doc/TODO0000664000175000017500000000033111237733167013573 0ustar bignosebignoseTo Do list for Burn: - volume id - audio normalization - tag decoder option - cdrecord -pad option - fill CD with as many MP3 as possible. farnis@despammed.com - WAV track info support - CD cover easy solution burn-0.4.6/doc/index.txt0000664000175000017500000002004311237733167014755 0ustar bignosebignose#### Burn #### .. rubric:: “Burn until recorded, now!” .. contents:: :depth: 2 About ===== Burn is a simple program/script written in Python. It's aim is to make it simple and quick to burn CDs or DVDs. First of all Burn... * ... has to be a command line program/script; * ... must perform any of its feature invoking it only once and with one and only one command line. Burn should be able to: * create Audio CD from .ogg, .mp3, .wav (even toghether) * copy CDs * create Data-CD or DVD (storage, backups, etc.) * create a CD or DVD from an existing ISO image. Burn still performs above features. Other things burn does: * size of stuff to be burned. * compute if there is enough free space for temporary files (images and audio files). * warns if size is bigger than CD/DVD capacity * convert .mp3 and .ogg in .wav without external decoders * perform burn simulations for testing * eject CD/DVD at the end of burn process * asks if you want to see what is going to be burned (mounts and umount image) * asks if you want to make other copies * shows main id3 tags for .mp3 and .ogg files Requirements ============ In order to work, burn requires: * `python`_ (version 2.4 or later) * `cdrkit`_ (wodim, genisoimage, cdrdao) * `eyeD3`_ * `pyogg`_ (python-pyvorbis) (generic Ogg Vorbis modules for Python). .. _python: http://www.python.org/ .. _cdrkit: http://cdrkit.org/ .. _eyeD3: http://eyed3.nicfit.net/releases/ .. _pyogg: http://ekyo.nerim.net/software/pyogg/ **Optionally**: you can choose not to decode mp3 and ogg files with native burn functions choosing to do it with external decoders. If you wanto to do so, you have to install preferred decoders (ogg123, mpg321, mpg123, etc.) and set up burn.conf. (remember: lame uses mpg123 to convert to wav). .. admonition:: Warning PowerPC users Internal decoder (mp3 to wav) does not work. Please use external decoding. Quick start =========== First of all, remember following points: 1. first argument has to be one of the burn modes: * ``-A`` for Audio-CD * ``-D`` for Data-CD/DVD * ``-I`` for ISO-CD/DVD * ``-C`` for Copy-CD/DVD 2. you can test burn commands using ``-s`` option after MODE, in this way you can always perform a CD/DVD writing simulation. So you don't have to waste CDs/DVDs to test it. Examples -------- Here are some commands that help you start using burn quickly. Creates a CD/DVD with /etc/ contents:: # burn -D -p /etc/ (you will find files and directories contained in /etc in CD's root.) Creates a CD/DVD with spain.tar.gz in CDs root:: # burn -D -p /home/bigpaul/video/summer_2003/spain.tar.gz Creates a CD/DVD containing the whole /etc/ directory:: # burn -D -r /etc/ (-r preserves path) Creates a CD/DVD containing the whole /home/bigpaul/Mail renamed into /mail_2003:: # burn -D -c /mail_2003 /home/bigpaul/Mail -p /boot/vmli* (-c changes path name). This command also adds in CD's root every vmli* file in /boot/ directory Burns image.iso:: # burn -I -n image.iso Copy CDs/DVDs:: # burn -C Creates an Audio CD. Tracks come from wav files:: # burn -A -a *.wav Creates an Audio CD. Tracks come from mp3 files:: # burn -A -a *.mp3 Creates an Audio CD. Tracks come from Ogg Vorbis files:: # burn -A -a *.ogg Creates an Audio CD. Tracks come from .wav, .ogg, .mp3 files:: # burn -A -a *.mp3 file.ogg track01.wav Status ====== Burn already works. But it is in the early developement stage. It already features: * Data-CD/DVD * Audio-CD * Copy-CD/DVD * ISO-CD/DVD * image mount and view for Data-CD/DVD and ISO-CD/DVD. * disk usage and prompt if you are going to overburn. ToDo ==== .. include:: TODO Download ======== Latest `stable version`_ (0.4.4) .. _stable version: http://www.bigpaul.org/burn/download/burn-0.4.4.tar.gz `Debian package`_ .. _Debian package: http://packages.debian.org/sid/burn/ Old CVS repository `hacknight CVS`_ .. _hacknight CVS: http://cvs.hacknight.org/cgi/viewcvs.cgi/burn/ My bzr branch at launchpad: ``bzr branch lp:~gaetano-paolone/+junk/burn/`` Ben Finney bzr branch: ``bzr checkout http://vcs.whitetree.org/bzr/public/burn/burn.devel/`` Main revisions are also avaible from `download directory`_ .. _download directory: http://www.bigpaul.org/burn/download/ Documentation ============= Here is the help page. Other documents coming soon:: usage: burn -MODE [general_option] [mode_option] ... For quick start you can get common examples with: burn -e options: -h, --help show this help message and exit -e, --examples show examples for quick startup Main burn MODES: The _have_ to be the first argument after program name -D, --data-cd creates a Data CD/DVD. -I, --iso-cd creates a CD/DVD from ISO. -C, --copy-cd copy CD/DVD. -A, --audio-cd creates an Audio CD from .wav, .mp3 and .ogg files. General options: These options could be used for every burn mode unless stated otherwise -s, --simulate This option will perform a burn simulation. -j, --eject This option will eject disk when burn process is over. Data CD Mode (-D) options: Data CD: adds files and directories. -p, --path add file/s or path's content to CD-ROM/DVD's root. e.g.: -p /cvs/myproj/ . In this example we will find CD-ROM/DVD's root filled with /cvs/myproj/ contents, but no /cvs/myproj/ will be created. -r, --preserve-path add file/s or path's content to CD-ROM/DVD preserving original path. e.g.: -r /cvs/myproj/ . In this example we will find /cvs/myproj/ in CD-ROM/DVD's root. -x, --exclude-path every file or directory matching this string will not be included. -cCHANGE_PATH, --change-path=CHANGE_PATH usage: -c . With this option, old_path will be named new_path in CD-ROM/DVD. e.g.: -c /my_home/2004_Jan/ /home/bigpaul/ . Thus /home/bigpaul/ will be named /my_home/2004_Jan/ in CD- ROM/DVD. -l, --follow-symlink this option allows burn to follow symbolic link directories -m, --multisession this option allows multisession CDs ISO CD Mode (-I) options: Creates a CD-ROM/DVD from an existing image. -nISO_NAME, --name=ISO_NAME image name Copy CD Mode (-C) options: If you have both a reader and a recording unit you can perform a copy on-the-fly. You can also copy a CD even if you only have the recording unit. Audio CD Mode (-A) options: Audio CD is used to create an audio CD-ROM from .wav, .ogg and .mp3 files. You can can use -a option to perform an Audio CD from different source audio files. -a, --audio-file .wav, .mp3, .ogg file/s. Files must have extensions (no matter if they are upper or lowercase). --audio-list=FILE_LIST m3u playlist or file with one audio file per line. --clear-audiotemp remove temporary audio files. Authors ======= .. include:: AUTHORS Thanks ====== * to Flexer for this layout * to zufus for his critical commit ;) * to baluba for having tried to make it work in python2.1 (unsuccesfully) * to pivi for audio duration hint * to farnis for his patient tests * to giuppi for his willness of removing system calls and his wonderful patches. * to ben finney for major code restyling .. Local variables: mode: rst coding: utf-8 time-stamp-format: "%:y-%02m-%02d" time-stamp-start: "^:Date:[ ]+" time-stamp-end: "$" time-stamp-line-limit: 20 End: vim: filetype=rst fileencoding=utf-8 : burn-0.4.6/doc/burn-flame.jpeg0000664000175000017500000001741611222766143016010 0ustar bignosebignoseJFIF,, XICC_PROFILE HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)KmPhotoshop 3.08BIMxHH(FG(HH(d'`8BIM,,8BIM x8BIM8BIM 8BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM@@8BIM8BIM JpbcJFIFHHAdobed            pJ"?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?I$$II+0$A6u3`N4\u.O83fɂPdK$R$JI$it.ʀ7,ӡKc~g)njCg%>)|bx=-6!~ >.[}{بDFɿ? ,cOkN[Ś&θWBw>xI-&EKSI$š9Kpht`UXh0x駨}hiCCWRց­>xsY]fol^o:ho{O?ApㆍUYo3G},q:)|Q$RR(k$I$&0:smt[s޽e8cdT?WtP_#3rgH~>)4#8Ƶ൏=0}K;ױ-ω$r'HRK}o}ҒUSI+wYZye2uoJ_K4k> |U?T8w緣0}x4VK#8ם3z yIv#͟#9șH>tzWFV]]0l6]ϭKǥ~^QoTxTuy:ORb8 ~k1DLbޝo!Q/Q_A\]uG[?WO8˧oy顿?Sg?W?礧I$RI$ɏs ]LyxLwJKl=m?s_EƤ$"Rо}0SP-Wl[psZJg_5{!ɰ<^/Jo. NɳI_N!'wvixjԥJ8BIMC     C yP  <1 !2AQqar"34BR#b5!1AQq"2abBR$4 ?=@Jx̳5$o $=nQY+KOQ),Jrd[/G@ 'cv}qFLdhJ6VM:]I%/j#-V|1['+R|Q{jjbq@sm}ʲ*h]$ hx"(εXӂuoۦ#fi[!Z]Fҧ)#olq}KpqUbk]ER5aQu3U 0! ˅ ~||Rd-b~ir;mNCy8{]V8sb\VQ|Raյ'ZRۦj-ԭ6GPҢwn#%gn:g*2I"9^G_c8Y@H (A)]{0iurwp_#9SպaF/ѡM|[I%(&.օR8jf66^-F en)Zv68>&ӻ6CC{SX\Bzeڑڲc~b@dt .4RUTʣeJ.)>CvCa>a\󝚳+3y:}߮KawФRR(kD̴KQN#xփ]:7Ϡ{h#dm hJ)"G96̸[\VFT&/ /Ir^<=oŦĦ<872}~)i])YB dv{7#e@$tt~A˝׷os=j&^n)G.`NQ!%jV0xXa‚]DT}U¨ aBBAv53`հRk?9K폫F֗b~IdVˉeNLm`> y :<]c:9>K%e;^h*X 0 !$vFYEe]g.p8pFߑZjk7eمsk>{cfuYn.cMs*F-L`wT#E˙T_aF-*k(8ᐡH)sU`^k54{+E`@+<1@Ne%9 _XxesUo89Qg*kasL${kFbm4ѨnFX,C{M=ڝFzm\hl'J\큐?w!)ٗYaQNUZE.0%@@,~vQ"UKcZ2\@o nv*KQ'h{cWrv4$]>ѯJ*ġ>>ϊpA>n~Ty384Ԁ}Ul̟,F<ٶh5>VW+[Vv{QSz'4[j<6zH^:80}Ogm)Yp96ߒ>ct1T*+[@URV8 PgAlUly共O0#=saj/ڽP┹+<ρv_1b/l72XqF3bJ?O)]ٚq).p}}ϊo$OCB t i- x}cWkگQ8wU@,xiZ˭2*]ѸjӡMoGN{M4~h[c*klg nS0T)y:ɧY٫`nvp>_ p+2בT|,llms~i׊>`) s~f Aj1UY sp U-21@*T@:!PJߊ  @UYburn-0.4.6/doc/AUTHORS0000664000175000017500000000021411237733167014153 0ustar bignosebignoseThe following people are primary contributors to Burn: * Gaetano Paolone * Ben Finney burn-0.4.6/doc/burn-configure.1.txt0000664000175000017500000000301211237733167016727 0ustar bignosebignose============== burn-configure ============== --------------------------------------- create a configuration file for burn(1) --------------------------------------- :Author: |author| :Date: 2009-07-24 :Copyright: This is free software: you may copy, modify, and/or distribute this work under the terms of the GNU General Public License, version 2 or later as published by the Free Software Foundation. No warranty expressed or implied. See the source for details. :Manual section: 1 :Manual group: Burn .. |command| replace:: burn-configure SYNOPSIS ======== |command| DESCRIPTION =========== |command| is a configuration utility for ``burn(1)``. Burn (“Burn until recorded, now!”) is a tool for writing optical media of various kinds. The ``burn(1)`` program looks for its configuration settings in the following files: ``/etc/burn.conf``, ``~/.burnrc``. When invoked, |command| will ask a series of questions interactively at the console, and then write a new configuration to the file ``burn.conf.new`` in the current directory. This file can then be used for the contents of any of the ``burn(1)`` configuration files. SEE ALSO ======== * ``burn(1)`` .. |author| replace:: Ben Finney , Gaetano Paolone .. Local variables: mode: rst coding: utf-8 time-stamp-format: "%:y-%02m-%02d" time-stamp-start: "^:Date:[ ]+" time-stamp-end: "$" time-stamp-line-limit: 20 End: vim: filetype=rst fileencoding=utf-8 : burn-0.4.6/doc/INSTALL0000664000175000017500000000772511237733167014152 0ustar bignosebignose############################### burn: Burn Until Recorded, Now! ############################### Installation ############ In order to use burn properly, please follow the following steps. Dependencies ============ Be sure you have installed properly: * python (>= 2.5) * wodim * genisoimage * cdrdao * eyeD3 (generic MP3 tag modules for Python) * pyogg (python-pyvorbis) (generic Ogg Vorbis modules for Python) Please refer to the documentation provided with the above programs in order to make them work. Optionally you can choose not to decode mp3 and ogg files with native burn functions choosing to do it with external decoders. If you want to do so, you have to install preferred decoders (lame, ogg123, mpg321, mpg123, etc.) and set up burn.conf. Warning powerpc users: internal decoder (mp3 to wav) does not work. Please use external decoding. Development dependencies ------------------------ For development work on Burn, you will need the following additional packages installed (each one available from the Debian package named in parentheses): * setuptools (``python-setuptools``) * nose (``python-nose``) * MiniMock (``python-minimock``) version 1.2.2 or later * Docutils (``python-docutils``) version 0.5 or later * Docutils manpage writer (``docutils-writer-manpage``) * Lynx (``lynx``) The distribution build program is ``setup.py`` in the source tree. Use this program to build, test, install, or any other actions permitted with `distutils`_ or `setuptools`_. .. _distutils: http://docs.python.org/install/ .. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools Package installation ==================== It is possible to unpack the source tarball and run the programs directly from the source directory. This limits you to only running the programs from a specific directory, though. If you want the programs available from anywhere on the system, you should install the package using Python's standard distutils installation method:: $ python ./setup.py install This will install executable programs and Python libraries to the default locations. Refer to the `distutils user documentation`_ for customisation of install location and other options. .. _distutils user documentation: http://docs.python.org/install/ Configuration file location =========================== (You can skip this step if you want to use burn from newly uncompressed directory. For example with point and slash in front of it: ./burn) * move configuration file (burn.conf) to your preferred location (e.g.: /etc/) * edit burn executable * find the line below the comment line: # CONFIGURATION FILE LOCATION ***************************** * set the absolute path for burn.conf file. e.g.: config.read(['/etc/burn.conf/']) Configure values ================ Edit configuration file (burn.conf) in order to change values to set your preferences. * In section [executables] please add full path to binaries. Here you can also enable external mp3 and ogg decoders. * In section [ISO] you'll have to specify: temporary image directory name, temporary image name, Joliet support, mount point * In section [CD-writer] you should configure your cd-writer device. Remember you can get LUN values for your (emulated or not) SCSI units with the following cdrecord command: # wodim -scanbus You can also choose to enable Buffer Underrun Free writing on with burnfree option. * In section [CD-reader] you have to configure your CD reader unit. If you only have a cd-writer unit, use the same cd-writer LUN * In section [Media] you can configure media's capacity. Ok, that's it. Now burn should be properly set up. try to launch it without any options. You should get help. Remember: you have to have *root privileges* in order to use burn. .. Local Variables: mode: text mode: rst coding: utf-8 End: vim: filetype=rst fileencoding=utf-8 expandtabs : burn-0.4.6/setup.py0000775000175000017500000002424611314277002014051 0ustar bignosebignose#! /usr/bin/python # -*- coding: utf-8 -*- # setup.py # Part of Burn, an optical media recording toolkit. # # Copyright © 2009 Ben Finney . # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA. """ Python distutils setup for ‘burn’ distribution. """ import distutils.cmd import distutils.command.build import distutils.command.install import distutils.command.clean import distutils.log import distutils.util import os import os.path import glob import errno import subprocess import re import textwrap from setuptools import setup, find_packages import docutils.core distribution_name = "burn" main_module_name = 'burnlib' main_module = __import__(main_module_name, fromlist=['version']) version = main_module.version main_module_doc = main_module.__doc__.decode('utf-8') short_description, long_description = ( textwrap.dedent(desc).strip() for desc in main_module_doc.split('\n\n', 1) ) def is_source_file_newer(source_path, destination_path, force=False): """ Return True if destination is older than source or does not exist. """ if force: result = True else: source_stat = os.stat(source_path) source_ctime = source_stat.st_ctime try: destination_stat = os.stat(destination_path) except OSError, exc: if exc.errno == errno.ENOENT: destination_ctime = None else: raise else: destination_ctime = destination_stat.st_ctime result = (source_ctime > destination_ctime) return result class BuildDocumentationCommand(distutils.cmd.Command): """ Build documentation for this distribution. """ user_options = [ ("force", 'f', "Forcibly build everything (ignore file timestamps)."), ("html-src-files=", None, "Source files to build to HTML documents."), ("manpage-src-files=", None, "Source files to build to Unix manpages."), ] boolean_options = ['force'] def initialize_options(self): """ Initialise command options to defaults. """ self.document_transforms = { 'html': { 'source_name_option': 'html_src_files', 'writer_name': 'html', 'source_suffix_regex': re.compile("\.txt$"), 'dest_suffix': ".html", }, 'manpage': { 'source_name_option': 'manpage_src_files', 'writer_name': 'manpage', 'source_suffix_regex': re.compile("\.1\.txt$"), 'dest_suffix': ".1", }, } self.force = None for transform in self.document_transforms.values(): option_name = transform['source_name_option'] setattr(self, option_name, None) def finalize_options(self): """ Finalise command options before execution. """ self.set_undefined_options( 'build', ('force', 'force')) for (transform_name, transform) in self.document_transforms.items(): source_paths = [] option_name = transform['source_name_option'] source_files_option_value = getattr(self, option_name, None) if source_files_option_value is not None: source_paths = source_files_option_value.split() transform['source_paths'] = source_paths def _render_documents(self, transform): """ Render documents that are not up-to-date. """ for in_file_path in transform['source_paths']: out_file_base = re.sub( transform['source_suffix_regex'], "", in_file_path) out_file_path = out_file_base + transform['dest_suffix'] if is_source_file_newer(in_file_path, out_file_path, self.force): out_file_dir = os.path.dirname(out_file_path) if not os.path.isdir(out_file_dir): self.mkpath(out_file_dir) render_rst_document(in_file_path, out_file_path, transform) def run(self): """ Execute this command. """ for transform in self.document_transforms.values(): self._render_documents(transform) class BuildCommand(distutils.command.build.build): """ Custom ‘build’ command for this distribution. """ sub_commands = ( [('build_doc', lambda self: True)] + distutils.command.build.build.sub_commands) class InstallCommand(distutils.command.install.install): """ Custom ‘install’ command for this distribution. """ sub_commands = ( [('build_doc', lambda self: True)] + distutils.command.install.install.sub_commands) def render_rst_document(in_file_path, out_file_path, transform): """ Render a document from source to dest using specified writer. """ in_file_path = distutils.util.convert_path(in_file_path) out_file_path = distutils.util.convert_path(out_file_path) writer = transform['writer_name'] distutils.log.info( "using writer %(writer)r to render document" " %(in_file_path)r -> %(out_file_path)r" % vars()) docutils.core.publish_file( source_path=in_file_path, destination_path=out_file_path, writer_name=transform['writer_name']) class BuildReadmeCommand(distutils.cmd.Command): """ Build README file for this distribution. """ user_options = [ ("input-file=", None, "Source HTML file."), ("output-file=", None, "Destination file to write."), ] def initialize_options(self): """ Initialise command options to defaults. """ self.input_file = "doc/index.html" self.output_file = "README" def finalize_options(self): """ Finalise command options before execution. """ def _render_html_to_text(self, in_file_path, out_file_path): """ Render HTML document to plain text. """ render_process_args = [ "lynx", "-dump", "-nolist", in_file_path, ] distutils.log.info( "rendering document %(in_file_path)r -> %(out_file_path)r" % vars()) out_file = open(out_file_path, 'w') subprocess.call(render_process_args, stdout=out_file) def run(self): """ Execute this command. """ self._render_html_to_text(self.input_file, self.output_file) class CleanDocumentationCommand(distutils.cmd.Command): """ Clean files generated for this distribution's documentation. """ user_options = [ ("all", 'a', "Remove all build output, not just temporary by-products."), ("generated-files=", None, "File globs of generated documentation files."), ] boolean_options = ['all'] def initialize_options(self): """ Initialise command options to defaults. """ self.all = None self.generated_files = None def finalize_options(self): """ Finalise command options before execution. """ self.set_undefined_options( 'clean', ('all', 'all')) if self.generated_files: self.generated_file_globs = self.generated_files.split() else: self.generated_file_globs = [] def run(self): """ Execute this command. """ generated_file_paths = set() for file_glob in self.generated_file_globs: generated_file_paths.update(set( glob.glob(file_glob))) for file_path in generated_file_paths: os.remove(file_path) class CleanCommand(distutils.command.clean.clean, object): """ Custom ‘clean’ command for this distribution. """ sub_commands = ( [('clean_doc', lambda self: True)] + distutils.command.clean.clean.sub_commands) def run(self): for cmd_name in self.get_sub_commands(): self.run_command(cmd_name) super(CleanCommand, self).run() setup( name=distribution_name, version=version.version, packages=["burnlib"], scripts=[ "bin/burn", "bin/burn-configure", ], cmdclass={ "build": BuildCommand, "build_doc": BuildDocumentationCommand, "build_readme": BuildReadmeCommand, "install": InstallCommand, "clean": CleanCommand, "clean_doc": CleanDocumentationCommand, }, # Setuptools metadata. zip_safe=False, install_requires=[ "setuptools", "docutils >=0.6", ], tests_require=[ "nose", "MiniMock >=1.2.2", ], test_suite="nose.collector", # PyPI metadata. author=version.author_name, author_email=version.author_email, maintainer="Ben Finney", maintainer_email="ben+python@benfinney.id.au", description=short_description, keywords=[ "burn", "cd", "dvd", "media", ], url=main_module._url, long_description=long_description, license=version.license, classifiers=[ # Reference: http://pypi.python.org/pypi?%3Aaction=list_classifiers "License :: OSI Approved :: GNU General Public License (GPL)", "Development Status :: 4 - Beta", "Intended Audience :: End Users/Desktop", "Environment :: Console", "Operating System :: POSIX", "Operating System :: MacOS", "Programming Language :: Python", "Topic :: Multimedia :: Sound/Audio :: CD Audio :: CD Writing", "Topic :: System :: Archiving", "Topic :: Utilities", ] ) # Local variables: # mode: python # coding: utf-8 # End: # vim: filetype=python fileencoding=utf-8 : burn-0.4.6/changelog0000664000175000017500000001313111314277607014210 0ustar bignosebignose2009-12-23 Ben Finney * Incorporate improvements to build process: * Build based on timestamps. * Clean based on file globs. * Customise main commands to include new sub-commands. 2009-12-22 Ben Finney * MANIFEST.in: Add distutils configuration file to distribution. 2009-09-20 Ben Finney * Remove some unused imports. * Improve code documentation and names. * Declare dependency on Docutils version that includes the ‘manpage’ writer. 2009-08-22 Ben Finney Version 0.4.5 released. * Fix invocation of rendering command for ‘README’ document. 2009-08-21 Ben Finney * Refactor interactive configuration dialogue to separate ‘interactive_configure’ module. * Implement child process invocations with ‘subprocess.Popen’. This is more consistent and secure than using string concatenation for the command line. 2009-07-26 Ben Finney * Parameterise file paths for ‘burn-configure’ program via command line OptionParser. 2009-07-24 Ben Finney * Remove default configuration file. 2009-07-23 Ben Finney * Generate HTML documents from reStructuredText source. * Generate man pages from reStructuredText source. 2009-07-16 Ben Finney * Consolidate configuration into ‘configure’ module. 2009-07-15 Ben Finney * Consolidate common console interactions into functions. 2009-07-10 Gaetano Paolone * Burn depends on cdrkit (no more on cdrtools). function names also changed in order to avoid misunderstandings. * Configuration can guess the optical media device settings. 2009-07-03 Ben Finney Version 0.4.4 released. * Update FSF address and copyright license text to latest revision of GPLv2 text. * Fix manpage markup and give useful ‘whatis’ descriptions. * Work on code base to conform to PEP 8. * Define scope better by avoiding use of global variables. * Move all functionality to functions inside library modules. Command-line interface now uses shell program wrappers. * Manage distribution using Python setuptools. 2005-03-17 Gaetano Paolone * added disk at once feature for audio tracks. So if used, no 2 seconds audo gap between (live) tracks. 2005-03-15 Gaetano Paolone Version 0.4.3 released. * closed lots of bug. * (closes: #252662) now empty media check is done only if it is a cd-writing command. No check for -e and -h options. * fixed typo in documentation (same bug above) * (closes: #257437) Now -x computes excluded size. It's been hard to solve it. I know there is better code for this, but it seems to work. * (closes: #271700) Fixed msinfo cdrecord parsing. Thanks to Bram Senders * (closes: #285802) escaped audio file names before cdrecord * (closes: #276414) valid answers specified. Thanks to Jochen Schulz * (closes: #280969) break added. Thanks to Stephan Beyer 2004-05-01 Gaetano Paolone Version 0.4.2 released. * Added giuppi's patch that enables progress bar. This feature was taken from python cookbook. * Now eject is no more eject system call (it was another dependency) but is done with cdrecord. * Verbose mkisofs output omitted because now we have a progress bar. 2004-04-27 Gaetano Paolone Version 0.4 released. * added --play-list option. With this option you can specify a m3u playlist file or a file with an audio file per row (ogg, mp3, wav); * Audio CD now prints an audio track summary with audio duration (partial an total) * Added --clear-audiotemp for Audio-CD. It removes temp audio files at the very beginning of burn process. * Now you can choose if you want to auto-check media capacity and if disk is empty. * added superuser(root) control. It can be overrided by ask_root in burn.conf * fixed problems for spaces in MP3 and OGG audio files in external audio decoding 2004-03-29 Gaetano Paolone Version 0.3 released. * added eject option (-j --eject) * modified simulate option for copy-cd * improved examples * copy-cd has now a valid simulate option * now if an option is put after another one which has variable arguments, an error is raised * now ti checks (and exit) if there is not enough free space on disk before checking if to_be_burned is bigger than media's capacity * added -pad option to cdrecord line (multiple of 2352 byte) * preparing gettext... just preparing * eyeD3 now is used. MP3Info is no longer used. * with ao python module we don't really need deoder anymore. code left quoted anyway... So no more mpg321 mpg123 ogg123 lame, etc. * Now you can choose between native decoding functions to get wavs from mp3 or ogg and external decodings (mpg123, mpg321, ogg123, lame) 2004-03-11 Gaetano Paolone Version 0.2 released. * initil release Copyright © 2009 Ben Finney . Copyright © 2004–2009 Gaetano Paolone . This work is free software: you are hereby free to redistribute this file, with or without modification, provided the copyright notice and this permission notice are preserved. Local variables: mode: changelog coding: utf-8 indent-tabs-mode: t End: vim: filetype=changelog fileencoding=utf-8 noexpandtab : burn-0.4.6/README0000644000175000017500000001732311314275276013223 0ustar bignosebignoseBurn "Burn until recorded, now!" Contents * About * Requirements * Quick start + Examples * Status * ToDo * Download * Documentation * Authors * Thanks About Burn is a simple program/script written in Python. It's aim is to make it simple and quick to burn CDs or DVDs. First of all Burn... * ... has to be a command line program/script; * ... must perform any of its feature invoking it only once and with one and only one command line. Burn should be able to: * create Audio CD from .ogg, .mp3, .wav (even toghether) * copy CDs * create Data-CD or DVD (storage, backups, etc.) * create a CD or DVD from an existing ISO image. Burn still performs above features. Other things burn does: * size of stuff to be burned. * compute if there is enough free space for temporary files (images and audio files). * warns if size is bigger than CD/DVD capacity * convert .mp3 and .ogg in .wav without external decoders * perform burn simulations for testing * eject CD/DVD at the end of burn process * asks if you want to see what is going to be burned (mounts and umount image) * asks if you want to make other copies * shows main id3 tags for .mp3 and .ogg files Requirements In order to work, burn requires: * python (version 2.4 or later) * cdrkit (wodim, genisoimage, cdrdao) * eyeD3 * pyogg (python-pyvorbis) (generic Ogg Vorbis modules for Python). Optionally: you can choose not to decode mp3 and ogg files with native burn functions choosing to do it with external decoders. If you wanto to do so, you have to install preferred decoders (ogg123, mpg321, mpg123, etc.) and set up burn.conf. (remember: lame uses mpg123 to convert to wav). Warning PowerPC users Internal decoder (mp3 to wav) does not work. Please use external decoding. Quick start First of all, remember following points: 1. first argument has to be one of the burn modes: + -A for Audio-CD + -D for Data-CD/DVD + -I for ISO-CD/DVD + -C for Copy-CD/DVD 2. you can test burn commands using -s option after MODE, in this way you can always perform a CD/DVD writing simulation. So you don't have to waste CDs/DVDs to test it. Examples Here are some commands that help you start using burn quickly. Creates a CD/DVD with /etc/ contents: # burn -D -p /etc/ (you will find files and directories contained in /etc in CD's root.) Creates a CD/DVD with spain.tar.gz in CDs root: # burn -D -p /home/bigpaul/video/summer_2003/spain.tar.gz Creates a CD/DVD containing the whole /etc/ directory: # burn -D -r /etc/ (-r preserves path) Creates a CD/DVD containing the whole /home/bigpaul/Mail renamed into /mail_2003: # burn -D -c /mail_2003 /home/bigpaul/Mail -p /boot/vmli* (-c changes path name). This command also adds in CD's root every vmli* file in /boot/ directory Burns image.iso: # burn -I -n image.iso Copy CDs/DVDs: # burn -C Creates an Audio CD. Tracks come from wav files: # burn -A -a *.wav Creates an Audio CD. Tracks come from mp3 files: # burn -A -a *.mp3 Creates an Audio CD. Tracks come from Ogg Vorbis files: # burn -A -a *.ogg Creates an Audio CD. Tracks come from .wav, .ogg, .mp3 files: # burn -A -a *.mp3 file.ogg track01.wav Status Burn already works. But it is in the early developement stage. It already features: * Data-CD/DVD * Audio-CD * Copy-CD/DVD * ISO-CD/DVD * image mount and view for Data-CD/DVD and ISO-CD/DVD. * disk usage and prompt if you are going to overburn. ToDo To Do list for Burn: * volume id * audio normalization * tag decoder option * cdrecord -pad option * fill CD with as many MP3 as possible. farnis@despammed.com * WAV track info support * CD cover easy solution Download Latest stable version (0.4.4) Debian package Old CVS repository hacknight CVS My bzr branch at launchpad: bzr branch lp:~gaetano-paolone/+junk/burn/ Ben Finney bzr branch: bzr checkout http://vcs.whitetree.org/bzr/public/burn/burn.devel/ Main revisions are also avaible from download directory Documentation Here is the help page. Other documents coming soon: usage: burn -MODE [general_option] [mode_option] ... For quick start you can get common examples with: burn -e options: -h, --help show this help message and exit -e, --examples show examples for quick startup Main burn MODES: The _have_ to be the first argument after program name -D, --data-cd creates a Data CD/DVD. -I, --iso-cd creates a CD/DVD from ISO. -C, --copy-cd copy CD/DVD. -A, --audio-cd creates an Audio CD from .wav, .mp3 and .ogg files. General options: These options could be used for every burn mode unless stated otherwise -s, --simulate This option will perform a burn simulation. -j, --eject This option will eject disk when burn process is over. Data CD Mode (-D) options: Data CD: adds files and directories. -p, --path add file/s or path's content to CD-ROM/DVD's root. e.g.: -p /cvs/myproj/ . In this example we will find CD-ROM/DVD's root filled with /cvs/myproj/ contents, but no /cvs/myproj/ will be created. -r, --preserve-path add file/s or path's content to CD-ROM/DVD preserving original path. e.g.: -r /cvs/myproj/ . In this example we will find /cvs/myproj/ in CD-ROM/DVD's root. -x, --exclude-path every file or directory matching this string will not be included. -cCHANGE_PATH, --change-path=CHANGE_PATH usage: -c . With this option, old_path will be named new_path in CD-ROM/DVD. e.g.: -c /my_home/2004_Jan/ /home/bigpaul/ . Thus /home/bigpaul/ will be named /my_home/2004_Jan/ in CD- ROM/DVD. -l, --follow-symlink this option allows burn to follow symbolic link directories -m, --multisession this option allows multisession CDs ISO CD Mode (-I) options: Creates a CD-ROM/DVD from an existing image. -nISO_NAME, --name=ISO_NAME image name Copy CD Mode (-C) options: If you have both a reader and a recording unit you can perform a copy on-the-fly. You can also copy a CD even if you only have the recording unit. Audio CD Mode (-A) options: Audio CD is used to create an audio CD-ROM from .wav, .ogg and .mp3 files. You can can use -a option to perform an Audio CD from different source audio files. -a, --audio-file .wav, .mp3, .ogg file/s. Files must have extensions (no matter if they are upper or lowercase). --audio-list=FILE_LIST m3u playlist or file with one audio file per line. --clear-audiotemp remove temporary audio files. Authors The following people are primary contributors to Burn: * Gaetano Paolone * Ben Finney Thanks * to Flexer for this layout * to zufus for his critical commit ;) * to baluba for having tried to make it work in python2.1 (unsuccesfully) * to pivi for audio duration hint * to farnis for his patient tests * to giuppi for his willness of removing system calls and his wonderful patches. * to ben finney for major code restyling burn-0.4.6/setup.cfg0000644000175000017500000000034111314307442014143 0ustar bignosebignose[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 [clean_doc] generated-files = doc/*.html doc/*.1 [build_doc] html-src-files = doc/index.txt doc/INSTALL manpage-src-files = doc/burn.1.txt doc/burn-configure.1.txt