diff options
Diffstat (limited to 'jack_functions.py')
-rwxr-xr-x | jack_functions.py | 463 |
1 files changed, 463 insertions, 0 deletions
diff --git a/jack_functions.py b/jack_functions.py new file mode 100755 index 0000000..21fe24c --- /dev/null +++ b/jack_functions.py @@ -0,0 +1,463 @@ +# -*- coding: iso-8859-15 -*- +### jack_functions: functions for +### jack - extract audio from a CD and encode it using 3rd party software +### Copyright (C) 1999-2003 Arne Zellentin <zarne@users.sf.net> + +### 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import codecs +import traceback +import sndhdr +import types +import string +import sys +import os + +import jack_TOCentry +import jack_CDTime +import jack_utils +import jack_TOC +import jack_mp3 +import jack_helpers + +from jack_globals import * + +progress_changed = None + +def df(fs = ".", blocksize = 1024): + "returns free space on a filesystem (in bytes)" + try: + from os import statvfs + statvfs_found = 1 + except: + statvfs_found = 0 + + if statvfs_found: + (f_bsize, f_frsize, f_blocks, f_bfree, f_bavail, f_files, f_ffree, f_favail, f_flag, f_namemax) = statvfs(fs) + return long(f_bavail) * long(f_bsize) + else: + # Not very portable + p = os.popen("df " + fs) + s = string.split(string.rstrip(p.readline())) + for i in range(len(s)): + if s[i] == "Available": + p.close() + s = string.split(string.rstrip(p.readline())) + return int(s[i]) * long(blocksize) - long(keep_free) + +def get_sysload_linux_proc(): + "extract sysload from /proc/loadavg, linux only (?)" + f = open("/proc/loadavg", "r") + loadavg = float(string.split(f.readline())[0]) + return loadavg + +def pprint_i(num, fmt = "%i%s", scale = 2.0**10, max = 4): + "return a string describing an int in a more human-readable way" + c = "" + change = 0 + for i in ("K", "M", "G", "T"): + if abs(num) >= scale: + c = i + num = num / scale + change = 1 + if change: + #num = num + 0.5 + if num > 999: + return fmt % (num, c) + elif num >= 100: + return string.replace(fmt, "%i", "%s") % (`num`[:3], c) + else: + return string.replace(fmt, "%i", "%s") % (`num`[:4], c) + else: + return fmt % (num, c) + +def pprint_speed(s, len=4): + if len >= 4: + if s < 10: + return "%4.2f" % s + if s < 100: + return "%4.1f" % s + if s < 1000: + return "%4.0f" % s + if s < 10000: + return "%4d" % (s + 0.5) + else: + return "9999" + elif len == 3: + if s < 10: + return "%3.1f" % s + if s < 100: + return "%3.0f" % s + if s < 1000: + return "%3d" % s + 0.5 + else: + return "999" + else: + return "X" * len + +def gettoc(toc_prog): + "Returns track list" + if jack_helpers.helpers[toc_prog].has_key('toc_cmd'): + cmd = string.replace(jack_helpers.helpers[toc_prog]['toc_cmd'], "%d", cf['_cd_device']) + if cf['_gen_device']: + cmd = string.replace(cmd, "%D", cf['_gen_device']) + p = os.popen(cmd) + start = 0 + erg = [] + l = p.readline() + exec(jack_helpers.helpers[toc_prog]['toc_fkt']) + if p.close(): + if cf['_cd_device']: + try: + f = open(cf['_cd_device'], "r") + except IOError: + info("could not open " + cf['_cd_device'] + ". Check permissions and that a disc is inserted.") + else: + info("maybe " + toc_prog + " is not installed?") + else: + info("try setting cf['_cd_device'] to your CD device, e.g. /dev/cdrom") + error("could not read CD's TOC.") + else: + return erg + else: + erg = [] + try: + exec(jack_helpers.helpers[toc_prog]['toc_fkt']) + except SystemExit: + sys.exit(1) + except: + traceback.print_exc() + error("""%s could not read the disk's TOC. If you already ripped the + CD, you'll have to cd into the directory which is either named + after the CD's title or still called jack-xxxxxxxx (xxxxxxxx is a + hex number).""" % toc_prog) + return erg + +def guesstoc(names): + "Return track list based on guessed lengths" + num = 1 + start = 0 + erg = [] + progr = [] + for i in names: + i_name = os.path.basename(i)[:-4] + i_ext = string.upper(os.path.basename(i)[-4:]) + if i_ext == ".MP3": + x = jack_mp3.mp3format(i) + if not x: + error("could not get MP3 info for file \"%x\"" % i) + blocks = int(x['length'] * CDDA_BLOCKS_PER_SECOND + 0.5) + # NUM, LEN, START, COPY, PRE, CH, RIP, RATE, + # NAME + erg.append([num, blocks, start, 0, 0, 2, 1, x['bitrate'], + i_name]) + progr.append([num, "dae", " * [ simulated ]"]) + progr.append([num, "enc", `x['bitrate']`, "[ s i m u l a t e d %3ikbit]" % (x['bitrate'] + 0.5)]) + if cf['_name'] % num != i_name: + progr.append([num, "ren", cf['_name'] % num + "-->" + i_name]) + elif i_ext == ".WAV": + x = sndhdr.whathdr(i) + if not x: + error("this is not WAV-format: " + i) + if x != ('wav', 44100, 2, -1, 16): + error("unsupportet format " + `x` + " in " + i) + blocks = jack_utils.filesize(i) + blocks = blocks - 44 # substract WAV header + extra_bytes = blocks % CDDA_BLOCKSIZE + if not extra_bytes == 0: + warning("this is not CDDA block-aligned: " + `i`) + yes = raw_input("May I strip %d bytes (= %.4fseconds) off the end? " % (extra_bytes, extra_bytes / 2352.0 / 75.0)) + if not string.upper((yes + "x")[0]) == "Y": + print "Sorry, I can't process non-aligned files (yet). Bye!" + sys.exit() + f = open(i, "r+") + f.seek(-extra_bytes, 2) + f.truncate() + f.close() + blocks = blocks - extra_bytes + blocks = blocks / CDDA_BLOCKSIZE + erg.append([num, blocks, start, 0, 0, 2, 1, cf['_bitrate'], i_name]) + progr.append([num, "dae", " =p [ s i m u l a t e d ]"]) + if cf['_name'] % num != i_name: + progr.append([num, "ren", cf['_name'] % num + "-->" + i_name]) + elif i_ext == ".OGG": + error("you still have to wait for ogg support for this operation, sorry.") + elif i_ext == ".FLAC": + error("you still have to wait for FLAC support for this ooperation, sorry.") + else: + error("this is neither .mp3 nor .ogg nor .wav nor .flac: %s" % i) + num = num + 1 + start = start + blocks + for i in progr: # this is deferred so that it is only written if no + # files fail + progress(i) + return erg + +#XXX will be moved to jack_convert +def timestrtoblocks(str): + "convert mm:ss:ff to blocks" + str = string.split(str, ":") + blocks = int(str[2]) + blocks = blocks + int(str[1]) * CDDA_BLOCKS_PER_SECOND + blocks = blocks + int(str[0]) * 60 * CDDA_BLOCKS_PER_SECOND + return blocks + +B_MM, B_SS, B_FF = 0, 1, 2 +def blockstomsf(blocks): + from jack_globals import CDDA_BLOCKS_PER_SECOND + "convert blocks to mm, ss, ff" + mm = blocks / 60 / CDDA_BLOCKS_PER_SECOND + blocks = blocks - mm * 60 * CDDA_BLOCKS_PER_SECOND + ss = blocks / CDDA_BLOCKS_PER_SECOND + ff = blocks % CDDA_BLOCKS_PER_SECOND + return mm, ss, ff, blocks + +def starts_with(str, with): + "checks whether str starts with with" + return str[0:len(with)] == with + +## #XXX the following will be used if all references to it have been updated. +## meanwhile the wrapper below is used. + +def real_cdrdao_gettoc(tocfile): # get toc from cdrdao-style toc-file + "returns TOC object, needs name of toc-file to read" + toc = jack_TOC.TOC() + + f = open(tocfile, "r") + + tocpath, tocfiledummy = os.path.split(tocfile) + +# a virtual track 0 is introduced which gets all of track 1s pregap. +# it is removed later if it is too small to contain anything interesting. + + actual_track = jack_TOCentry.TOCentry() + actual_track.number = 0 + actual_track.type = "audio" + actual_track.channels = 2 + actual_track.media = "image" + actual_track.start = 0 + actual_track.length = 0 + actual_track.rip = 1 + actual_track.bitrate = cf['_bitrate'] + actual_track.image_name = "" + actual_track.rip_name = cf['_name'] % 0 + +## tocfile data is read in line by line. + + num = 0 + while 1: + line = f.readline() + if not line: + if actual_track.channels not in [1,2,4]: + debug("track %02d: unknown number of channels, assuming 2" % num) + actual_track.channels = 2 + toc.append(actual_track) + break + line = string.strip(line) + +## everytime we encounter "TRACK" we increment num and append the actual +## track to the toc. + + if starts_with(line, "TRACK "): + num = num + 1 + new_track = jack_TOCentry.TOCentry() + new_track.number = num + if actual_track: + if actual_track.channels not in [1,2,4]: + debug("track %02d: unknown number of channels, assuming 2" % num) + actual_track.channels = 2 + toc.append(actual_track) + actual_track = new_track + actual_track.rip = 1 + actual_track.bitrate = cf['_bitrate'] + actual_track.start = toc.end_pos + if line == "TRACK AUDIO": + actual_track.type = "audio" + else: + actual_track.type = "other" # we don't care + actual_track.channels = 0 + actual_track.rip = 0 + actual_track.bitrate = 0 + +## check the various track flags. +## FOUR_CHANNEL_AUDIO is not supported. +## we have to check for this before ripping. later. much later. + + elif line == "NO COPY": + actual_track.copy = 0 + elif line == "COPY": + actual_track.copy = 1 + elif line == "NO PRE_EMPHASIS": + actual_track.preemphasis = 0 + elif line == "PRE_EMPHASIS": + actual_track.preemphasis = 1 + elif line == "TWO_CHANNEL_AUDIO": + actual_track.channels = 2 + elif line == "FOUR_CHANNEL_AUDIO": + actual_track.channels = 4 + +## example: FILE "data.wav" 08:54:22 04:45:53 + + elif starts_with(line, "FILE "): + filename = line[string.find(line, "\"") + 1:string.rfind(line, "\"")] + offsets = string.strip(line[string.rfind(line, "\"") + 1:]) + start, length = string.split(offsets)[:2] + +## convert time string to blocks(int), update info. + + actual_track.length = jack_CDTime.CDTime(length).blocks + actual_track.image_name = os.path.join(tocpath, filename) + actual_track.rip_name = cf['_name'] % num + +## example: START 00:01:53. This means the actual track starts 1:53s _after_ +## the start given by the FILE statement. This so-called pregap needs to be +## added to the length of the previous track, added to the start of the +## actual track and subtracted from its length. This is done automagically +## by setting the pregap attribute. + + elif starts_with(line, "START "): + actual_track.pregap = jack_CDTime.CDTime(string.split(line)[1]).blocks + + f.close() + return toc + + +def cdrdao_gettoc(tocfile): # get toc from cdrdao-style toc-file + "just a wrapper for real_cdrdao_gettoc." + toc = real_cdrdao_gettoc(tocfile) + tracks = toc.export() + track1_pregap = tracks[0][1] + use_filename = toc.image_file + # note: toc.image_file is None if different files are specified + return tracks[1:], use_filename, track1_pregap + + +##XXX this will be moved to jack_convert +def msftostr(msf): + "convert msf format to readable string" + return "%02i" % msf[B_MM]+":"+"%02i" % msf[B_SS]+":"+"%02i" % msf[B_FF] + +def cdrdao_puttoc(tocfile, tracks, cd_id): # put toc to cdrdao toc-file + "writes toc-file from tracks" + f = open(tocfile, "w") + f.write("CD_DA\n\n") + f.write("// DB-ID=" + cd_id + "\n\n") + for i in tracks: + f.write("// Track " + `i[NUM]` + "\n") # comments are cool + if i[CH] in (2, 4): + f.write("TRACK AUDIO\n") + if i[CH] == 0: + f.write("TRACK MODE1\n") + if i[COPY]: + f.write("COPY\n") + else: + f.write("NO COPY\n") + if i[PRE]: + f.write("PRE_EMPHASIS\n") + else: + f.write("NO PRE_EMPHASIS\n") + if i[CH] == 2: + f.write("TWO_CHANNEL_AUDIO\n") + elif i[CH] == 4: + f.write("FOUR_CHANNEL_AUDIO\n") + elif i[CH] == 0: + f.write("// not supported by jack!\n") + else: + error("illegal TOC: channels=%i, aborting." % i[CH]) + f.write('FILE "' + i[NAME] + '.wav" 0 ') + x = i[LEN] + if i[NUM] == 1: # add pregap to track, virtually + x = x + i[START] + x = blockstomsf(x) + f.write("%02i" % x[B_MM] + ":" + "%02i" % x[B_SS] + ":" + "%02i" % x[B_FF] + "\n") + if i[NUM]==1 and i[START] != 0: + f.write("START "+msftostr(blockstomsf(i[START]))+"\n") + f.write("\n") + +def tracksize(list, dont_dae = [], blocksize = 1024): + "Calculates all kind of sizes for a track or a list of tracks." + if list and type(list[0]) == types.IntType: + list = ((list, )) + peak, at, blocks = 0, 0, 0 + encoded_size = wavsize = cdrsize = 0 + for track in list: + blocks = blocks + track[LEN] + encoded_size = encoded_size + track[LEN] / CDDA_BLOCKS_PER_SECOND * track[RATE] * 1000 / 8 + # files can be a bit shorter, if someone knows a better way of guessing + # filesizes, please let me know. + count_thiswav = 1 + for i in dont_dae: + if i[NUM] == track[NUM]: + count_thiwav = 0 + thiscdrsize = track[LEN] * CDDA_BLOCKSIZE * count_thiswav + wavsize = wavsize + thiscdrsize + 44 + cdrsize = cdrsize + thiscdrsize + now = encoded_size + thiscdrsize + 44 + if now>peak: + at = track[NUM] + peak = now + return encoded_size, wavsize, encoded_size + wavsize, peak, at, cdrsize, blocks + +def progress(track, what="error", data="error", data2 = None): + "append a line to the progress file" + global progress_changed + if type(track) in (types.TupleType, types.ListType): + if len(track) == 3: + track, what, data = track + elif len(track) == 4: + track, what, data, data2 = track + else: + error("illegal progress entry:" + `track` + " (" + `type(track)` + ")") + + if type(track) == types.IntType: + first = "%02i" % track + elif type(track) == types.StringType: + first = track + else: + error("illegal progress entry:" + `track` + " (" + `type(track)` + ")") + progress_changed = 1 + f = codecs.open (cf['_progress_file'], "a", "utf-8") + f.write(first + cf['_progr_sep'] + what + cf['_progr_sep'] + data) + if data2: + f.write(cf['_progr_sep'] + data2) + f.write("\n") + f.close() + +def check_genre_txt(genre): + if isinstance(genre, int): + if genre in range(0,256): + return genre + else: + return None + + elif isinstance(genre, str): + if string.upper(genre) == "HELP": + info("available genres: " + string.join([x for x in eyeD3.genres if x != 'Unknown'], ", ")) + sys.exit(0) + elif string.upper(genre) == "NONE": + return 255 # set genre to [unknown] + else: + try: + genre = int(genre) + genre = check_genre_txt(genre) + if isinstance(genre, int): + return genre + except: + for i in range(len(id3genres)): + if genre.upper() == id3genres[i].upper(): + return i + + import jack_version + error("illegal genre. Try '" + jack_version.prog_name + " --id3-genre help' for a list.") |