I'm writing a program that fetches metadata from FLAC fliles and batch renames them. To do so, I'm using the py library.
Here is my code:
#!/usr/bin/python
# -*- coding: utf-8 -*-
# This program takes the information from FLAC metadata to rename the files
# according to various naming paterns.
"""
rename-flac takes the information from FLAC metadata to batch rename
the files according to various naming paterns.
Usage:
rename-flac.py (-s | -v) <directory>
rename-flac.py (-h | --help)
rename-flac.py --version
Options:
-h --help Show the help screen
-- version Outputs version information and exits
-s Define album as single artist
-v Define album as various artist
"""
from docopt import docopt
import subprocess
import sys
import os
from py.path import local
# Dependency check
programlist = ["flac", "python-py", "python-docopt"]
for program in programlist:
pipe = subprocess.Popen(
["dpkg", "-l", program], stdout=subprocess.PIPE)
dependency, error = pipe.communicate()
if pipe.returncode:
print """
%s is not installed: this program won't run correctly.
To instal %s, run: aptitude install %s
""" % (program, program, program)
sys.exit()
else:
pass
# Defining the function that fetches metadata and formats it
def metadata(filename):
filename = str(filename).decode("utf-8")
pipe = subprocess.Popen(
["metaflac", "--show-tag=tracknumber", filename],
stdout=subprocess.PIPE)
tracknumber, error = pipe.communicate()
tracknumber = tracknumber.decode("utf-8")
tracknumber = tracknumber.replace("tracknumber=", "")
tracknumber = tracknumber.replace("TRACKNUMBER=", "")
tracknumber = tracknumber.rstrip() # Remove whitespaces
if int(tracknumber) < 10:
if "0" in tracknumber:
pass
else:
tracknumber = "0" + tracknumber
else:
pass
pipe = subprocess.Popen(
["metaflac", "--show-tag=title", filename],
stdout=subprocess.PIPE)
title, error = pipe.communicate()
title = title.decode("utf-8")
title = title.replace("TITLE=", "")
title = title.replace("title=", "")
title = title.rstrip()
pipe = subprocess.Popen(
["metaflac", "--show-tag=artist", filename],
stdout=subprocess.PIPE)
artist, error = pipe.communicate()
artist = artist.decode("utf-8")
artist = artist.replace("ARTIST=", "")
artist = artist.replace("artist=", "")
artist = artist.rstrip()
return tracknumber, title, artist
# Defining function that renames the files
def rename(root):
if output == str(filename.purebasename).decode("utf-8"):
print "%s is already named correctly\n" % (title)
else:
filename.rename(filename.new(purebasename=output))
# Importing command line arguments
args = docopt(__doc__, version="rename-flac 0.5")
for option, value in args.iteritems():
global root, choice
if option == "<directory>":
root = local(value)
elif option == "-s" and value == True:
choice = 1
elif option == "-v" and value == True:
choice = 2
else:
pass
# 1 - Single artist
# File naming partern: TRACKNUMBER - TITLE.flac
if choice == 1:
for filename in root.visit(fil="*.flac", rec=True):
tracknumber, title, artist = metadata(filename)
output = "%s - %s" % (tracknumber, title)
rename(root)
print "Files renamed"
else:
pass
# 2 - Various artists
# File naming pattern: TRACKNUMBER - ARTIST - TITLE.flac
if choice == 2:
for filename in root.visit(fil="*.flac", rec=True):
tracknumber, title, artist = metadata(filename)
output = "%s - %s - %s" % (tracknumber, artist, title)
rename(root)
print "Files renamed"
else:
pass
My code runs fine when filename has utf-8 characters, but when the path to filename has utf-8 characters it get this error message:
Traceback (most recent call last):
File "/media/Main/Programmes/Rename_FLAC/rename-flac.py", line 122, in <module>
rename(root)
File "/media/Main/Programmes/Rename_FLAC/rename-flac.py", line 97, in rename
filename.rename(filename.new(purebasename=output))
File "/usr/lib/python2.7/dist-packages/py/_path/local.py", line 273, in new
"%(dirname)s%(sep)s%(basename)s" % kw)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 28: ordinal not in range(128)
This may seem obvious to more experienced programmers, but I have been trying to resolve that error for a few hours now...
This was the problem:
filename.rename(filename.new(purebasename=output)) crashed because output was a unicode mess.
output was created by joining tracknumber, artist and title. In metadata(filename) I was reencoding them as unicode with string = string.decode("utf-8"), but they were already in unicode, resulting in output being mess-up and the crash.
So I deleted string = string.decode("utf-8") and it works.
yay.
Related
The Python script below has worked for me in converting multiple PDF documents to a single PDF when running on Python 2.7. I'm now trying to use it on 3.10 and am getting errors.
I believe I've conquered most of them, especially changing print to print( and disabling imports of CoreFoundation and Quartz.CoreGraphics.
It seems that the only remaining error is:
line 85, in main
writeContext = CGPDFContextCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault,
arg, len(arg), False), None, None) NameError: name
'CGPDFContextCreateWithURL' is not defined
If I declare CGPDFContextCreateWithURL as an empty global, the error shifts to CFURLCreateFromFileSystemRepresentation. Declare that and the error is kCFAllocatorDefault.
Here's how I'm trying to handle those.
global CGPDFContextCreateWithURL CGPDFContextCreateWithURL = ""
I don't understand that entire line so any help in making it right would be appreciated.
#
# join
# Joing pages from a a collection of PDF files into a single PDF file.
#
# join [--output <file>] [--shuffle] [--verbose]"
#
# Parameter:
#
# --shuffle
# Take a page from each PDF input file in turn before taking another from each file.
# If this option is not specified then all of the pages from a PDF file are appended
# to the output PDF file before the next input PDF file is processed.
#
# --verbose
# Write information about the doings of this tool to stderr.
#
import sys
import os
import getopt
import tempfile
import shutil
# from CoreFoundation import *
# from Quartz.CoreGraphics import *
global verbose
verbose = False
def createPDFDocumentWithPath(path):
if verbose:
print("Creating PDF document from file %s" % (path))
return CGPDFDocumentCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, path, len(path), False))
def writePageFromDoc(writeContext, doc, pageNum):
page = CGPDFDocumentGetPage(doc, pageNum)
if page:
mediaBox = CGPDFPageGetBoxRect(page, kCGPDFMediaBox)
if CGRectIsEmpty(mediaBox):
mediaBox = None
CGContextBeginPage(writeContext, mediaBox)
CGContextDrawPDFPage(writeContext, page)
CGContextEndPage(writeContext)
if verbose:
print("Copied page %d from %s" % (pageNum, doc))
def shufflePages(writeContext, docs, maxPages):
for pageNum in xrange(1, maxPages + 1):
for doc in docs:
writePageFromDoc(writeContext, doc, pageNum)
def append(writeContext, docs, maxPages):
for doc in docs:
for pageNum in xrange(1, maxPages + 1) :
writePageFromDoc(writeContext, doc, pageNum)
def main(argv):
global verbose
# The PDF context we will draw into to create a new PDF
writeContext = None
# If True then generate more verbose information
source = None
shuffle = False
# Parse the command line options
try:
options, args = getopt.getopt(argv, "o:sv", ["output=", "shuffle", "verbose"])
except getopt.GetoptError:
usage()
sys.exit(2)
for option, arg in options:
if option in ("-o", "--output") :
if verbose:
print("Setting %s as the destination." % (arg))
writeContext = CGPDFContextCreateWithURL(CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, arg, len(arg), False), None, None)
elif option in ("-s", "--shuffle") :
if verbose :
print("Shuffle pages to the output file.")
shuffle = True
elif option in ("-v", "--verbose") :
print("Verbose mode enabled.")
verbose = True
else :
print("Unknown argument: %s" % (option))
if writeContext:
# create PDFDocuments for all of the files.
docs = map(createPDFDocumentWithPath, args)
# find the maximum number of pages.
maxPages = 0
for doc in docs:
if CGPDFDocumentGetNumberOfPages(doc) > maxPages:
maxPages = CGPDFDocumentGetNumberOfPages(doc)
if shuffle:
shufflePages(writeContext, docs, maxPages)
else:
append(writeContext, docs, maxPages)
CGPDFContextClose(writeContext)
del writeContext
#CGContextRelease(writeContext)
def usage():
print("Usage: join [--output <file>] [--shuffle] [--verbose]")
if __name__ == "__main__":
main(sys.argv[1:])
I'm new to python and having some issues with blocking. I have a script that I'm calling with options. I'm able to see the arguments come in, however, I have been unable to get the program to work correctly. In the code sample below, I'm trying to grab the arguments and then run the piece of code after the "#if ip address is not defined qpid-route will not work" comment. If I change the indentation after the comment, I get expected indentation or unexpected indentation errors.
The problem is that the way the code currently is it will run the elif opt in ("-i", "--ipaddress"): code and then will continue and run the code through to the bottom and then come back and run the -s loop code and then rerun the code to the bottom.
To fix this, I tried a break or continue command and all I get is indentation errors on this no matter which level I align it with. Can someone help me format this correctly such that I can pull the ipaddress and scac values that I'm grabbing from the arguments and then run the code after the "#if ip address is not defined qpid-route will not work" comment as a separate block.
import re
import os
import sys
import getopt
import pdb
ipaddress = ""
scac = ''
def main(argv):
#print argv
ipaddress = ""
scac = ''
pdb.set_trace()
try:
opts, args = getopt.getopt(argv,"hi:s:",["ipaddress=","scac="])
if not opts: # if no option given
print 'usage test.py -i <ipaddress> -s <scac>'
sys.exit(2)
except getopt.GetoptError:
print 'test.py -i <ipaddress> -s <scac>'
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print 'FedConnectStatus.py -i <iobipaddress> -s <scac>'
sys.exit() # it should be on level of if
elif opt in ("-i", "--ipaddress"):
ipaddress = arg
#break
#continue
elif opt in ("-s", "--scac"):
scac = arg
#if ip address is not defined qpid-route will not work
if not ipaddress:
print 'ip address needed'
else:
print(os.getcwd())
#If no scac is given grab every federated connection and report
if not scac:
# open file to read
f = file('qpid.txt', 'r')
nameList = []
statusList = []
#skip first 4 lines
for i in range(3): f.next() # skip first four lines
# iterate over the lines in the file
for line in f:
# split the line into a list of column values
columns = line.split(None, 5)
# clean any whitespace off the items
columns = [col.strip() for col in columns]
# ensure the column has at least one value before printing
if columns:
#print "Name", columns[0] # print the first column
#print "Status", columns[4] # print the last column
nameList.append(columns[0])
statusList.append(columns[4])
#print name
#print status
#else:
print nameList
print statusList
#if __name__ == "__main__":
main(sys.argv[1:])
This statement if not scac: on line 40 and below has indentation different to that of the rest of the code.
You'll see line 38 also doesn't match the indenting of the above if.
It could be I'm completely misunderstanding the getopt module
I am trying to parse [--magic-m] to my program, but it does not set the correct field.
Part of Encrypt Function
def encrypt(filename, text, magic):
if not magic is None:
hash = pbkdf2_sha256.encrypt(magic, rounds=10000, salt_size=16)
print pbkdf2_sha256.verify(magic, hash)
try:
d = load_image( filename )
except Exception,e:
print str(e)
Part of Load function
def load_image( filename ) :
img = Image.open( os.path.join(__location__, filename) )
img.load()
data = np.asarray( img, dtype="int32" )
return data
Main
if __name__ == "__main__":
if not len(sys.argv[1:]):
usage()
try:
opts,args = getopt.getopt(sys.argv[1:],"hedm:",["help", "encrypt", "decrypt", "magic="])
except getopt.GetoptError as err:
print str(err)
usage()
magic = None
for o,a in opts:
if o in ("-h","--help"):
usage()
elif o in ("-e","--encrypt"):
to_encrypt = True
elif o in ("-d","--decrypt"):
to_encrypt = False
elif o in ("-m", "--magic"):
magic = a
else:
assert False,"Unhandled Option"
print magic
if not to_encrypt:
filename = sys.argv[2]
decrypt(filename, magic)
else:
filename = sys.argv[2]
text = sys.argv[3]
encrypt(filename, text, magic)
I tried calling the program above like this:
[1] python stego.py -e test.jpeg lol -m h
or like this:
[2] python stego.py -e -m h test.jpeg lol
Output becomes:
[1] None
[2] lol
[2] True
[2] [Errno 2] No such file or directory: 'C:\\Users\\Educontract\\Steganography\\-m'
Whitout the option -m everything works fine
The colon should come after m to indicate that it requires an argument. You should also include an equals sign after the long option magic to indicate that it requires an argument.
getopt.getopt(sys.argv[1:],"hedm:",["help", "encrypt", "decrypt", "magic="])
You should put all your options before the arguments, as in your second example.
python stego.py -e -m h test.jpeg lol
If you print sys.argv, I think you'll find that sys.argv[2] and sys.argv[3] are not what you expect. I would fetch the arguments from args, rather than sys.argv.
filename = args[0]
text = args[1]
Note that you may find it easier to use the argparse library instead of getopt. It isn't as strict about requiring options before arguments.
How does the "Module: CLI (argparse)" template in Pydev (Eclipse) work?
When I run it, it just prints out the usage help and exits, and my code doesn't get executed. As I understand it from usage output, I need to pass a 'path' argument. But I got the same result when setting the path to something.
The template looks as follows:
#!/usr/local/bin/python2.7
# encoding: utf-8
'''
test1.test -- shortdesc
test1.test is a description
It defines classes_and_methods
#author: user_name
#copyright: 2015 organization_name. All rights reserved.
#license: license
#contact: user_email
#deffield updated: Updated
'''
import sys
import os
from argparse import ArgumentParser
from argparse import RawDescriptionHelpFormatter
__all__ = []
__version__ = 0.1
__date__ = '2015-07-31'
__updated__ = '2015-07-31'
DEBUG = 1
TESTRUN = 0
PROFILE = 0
class CLIError(Exception):
'''Generic exception to raise and log different fatal errors.'''
def __init__(self, msg):
super(CLIError).__init__(type(self))
self.msg = "E: %s" % msg
def __str__(self):
return self.msg
def __unicode__(self):
return self.msg
def main(argv=None): # IGNORE:C0111
'''Command line options.'''
if argv is None:
argv = sys.argv
else:
sys.argv.extend(argv)
program_name = os.path.basename(sys.argv[0])
program_version = "v%s" % __version__
program_build_date = str(__updated__)
program_version_message = '%%(prog)s %s (%s)' % (program_version, program_build_date)
program_shortdesc = __import__('__main__').__doc__.split("\n")[1]
program_license = '''%s
Created by user_name on %s.
Copyright 2015 organization_name. All rights reserved.
Licensed under the Apache License 2.0
http://www.apache.org/licenses/LICENSE-2.0
Distributed on an "AS IS" basis without warranties
or conditions of any kind, either express or implied.
USAGE
''' % (program_shortdesc, str(__date__))
try:
# Setup argument parser
parser = ArgumentParser(description=program_license, formatter_class=RawDescriptionHelpFormatter)
parser.add_argument("-r", "--recursive", dest="recurse", action="store_true", help="recurse into subfolders [default: %(default)s]")
parser.add_argument("-v", "--verbose", dest="verbose", action="count", help="set verbosity level [default: %(default)s]")
parser.add_argument("-i", "--include", dest="include", help="only include paths matching this regex pattern. Note: exclude is given preference over include. [default: %(default)s]", metavar="RE" )
parser.add_argument("-e", "--exclude", dest="exclude", help="exclude paths matching this regex pattern. [default: %(default)s]", metavar="RE" )
parser.add_argument('-V', '--version', action='version', version=program_version_message)
parser.add_argument(dest="paths", help="paths to folder(s) with source file(s) [default: %(default)s]", metavar="path", nargs='+')
# Process arguments
args = parser.parse_args()
paths = args.paths
verbose = args.verbose
recurse = args.recurse
inpat = args.include
expat = args.exclude
if verbose > 0:
print("Verbose mode on")
if recurse:
print("Recursive mode on")
else:
print("Recursive mode off")
if inpat and expat and inpat == expat:
raise CLIError("include and exclude pattern are equal! Nothing will be processed.")
for inpath in paths:
### do something with inpath ###
print(inpath)
return 0
except KeyboardInterrupt:
### handle keyboard interrupt ###
return 0
except Exception, e:
if DEBUG or TESTRUN:
raise(e)
indent = len(program_name) * " "
sys.stderr.write(program_name + ": " + repr(e) + "\n")
sys.stderr.write(indent + " for help use --help")
return 2
if __name__ == "__main__":
if DEBUG:
sys.argv.append("-h")
sys.argv.append("-v")
sys.argv.append("-r")
if TESTRUN:
import doctest
doctest.testmod()
if PROFILE:
import cProfile
import pstats
profile_filename = 'test1.test_profile.txt'
cProfile.run('main()', profile_filename)
statsfile = open("profile_stats.txt", "wb")
p = pstats.Stats(profile_filename, stream=statsfile)
stats = p.strip_dirs().sort_stats('cumulative')
stats.print_stats()
statsfile.close()
sys.exit(0)
sys.exit(main())
In DEBUG mode, the -h added to the sys.argv. That has priority over all other inputs, producing the help message (and exit).
Comment out thE line that adds the -h to DEBUG to see the effect of your arguments (plus v and r).
In other modes (except those defined in the main), the commandline arguments should have effect (i.e. the ones in sys.argv).
You're running in DEBUG mode and all you do in that mode is append stuff to argv:
if DEBUG:
sys.argv.append("-h") # <-- that is your problem
sys.argv.append("-v")
sys.argv.append("-r")
Since you're adding an "unknown" argument, when you run:
args = parser.parse_args()
it raises an exception. You can either solve it by removing that line (preferably), or by using:
parser.parse_known_args([])
(the latter is a patch to hide the underlying issue and should be used with cautious. IMO it shouldn't be used in this case!).
I am trying to learn Python, and thought I'd learn by writing something I'd actually use. SO I'm trying to write a little script to rip some music CDs.
I am using the musicbrainzngs package. I would like to get the tracklist of the CD. My code currently:
#! /usr/bin/env python
import argparse
import musicbrainzngs
import discid
musicbrainzngs.set_useragent("Audacious", "0.1", "https://github.com/jonnybarnes/audacious")
parser = argparse.ArgumentParser()
parser.add_argument("--cdrom", help="provide the source of the cd", default="/dev/cdrom")
args = parser.parse_args()
device = args.cdrom
print("device: %s" % device)
disc = discid.read(device)
print("id: %s" % disc.id)
try:
result = musicbrainzngs.get_releases_by_discid(disc.id, includes=["artists"])
except musicbrainzngs.ResponseError:
print("disc not found or bad response")
else:
if result.get("disc"):
print("artist:\t%s" %
result["disc"]["release-list"][0]["artist-credit-phrase"])
print("title:\t%s" % result["disc"]["release-list"][0]["title"])
elif result.get("cdstub"):
print("artist:\t" % result["cdstub"]["artist"])
print("title:\t" % result["cdstub"]["title"])
How can I get the tracklist, looking at the full results returned there is a track-list property but regardless of what CD I try the result is always empty
Getting releases by discid is a lookup and its "'inc=' arguments supported are identical to a lookup request for a release" which are listed earlier on that page. To get a non-empty tracklist you simply need to add the "recordings" include:
result = musicbrainzngs.get_releases_by_discid(disc.id, includes=["artists", "recordings"])
This is an example script for getting the tracklist for an album using musicbrainzngs
#!/usr/bin/python3
from __future__ import print_function
from __future__ import unicode_literals
import musicbrainzngs
import sys
musicbrainzngs.set_useragent(
"python-musicbrainzngs-example",
"0.1",
"https://github.com/alastair/python-musicbrainzngs/",
)
def get_tracklist(artist, album):
result = musicbrainzngs.search_releases(artist=artist, release=album, limit=1)
id = result["release-list"][0]["id"]
#### get tracklist
new_result = musicbrainzngs.get_release_by_id(id, includes=["recordings"])
t = (new_result["release"]["medium-list"][0]["track-list"])
for x in range(len(t)):
line = (t[x])
print(f'{line["number"]}. {line["recording"]["title"]}')
if __name__ == '__main__':
### get first release
if len(sys.argv) > 1:
artist, album = [sys.argv[1], sys.argv[2]]
get_tracklist(artist, album)
else:
artist = input("Artist: ")
album = input("Album: ")
if not artist == "" and not album == "":
get_tracklist(artist, album)
else:
print("Artist or Album missing")
Usage:
python3 album_get_tracklist.py "rolling stones" "beggars banquet"
or
python3 album_get_tracklist.py
it will ask for Artist and Album