Read Programming Python Online

Authors: Mark Lutz

Tags: #COMPUTERS / Programming Languages / Python

Programming Python (45 page)

Playing Media Files

We have
space for just one last, quick example in this chapter, so
we’ll close with a bit of fun. Did you notice how the file extensions for
text and binary file types were hard-coded in the directory search scripts
of the prior two sections? That approach works for the trees they were
applied to, but it’s not necessarily complete or portable. It would be
better if we could deduce file type from file name automatically. That’s
exactly what Python’s
mimetypes
module can do
for us. In this section, we’ll use it to build a script that attempts to
launch a file based upon its media type, and in the process develop
general tools for opening media portably with specific or generic
players.

As we’ve seen, on Windows this task is trivial—the
os.startfile
call opens files per the Windows
registry, a system-wide mapping of file extension types to handler
programs. On other platforms, we can either run specific media handlers
per media type, or fall back on a resident web browser to open the file
generically using Python’s
webbrowser
module.
Example 6-23
puts these ideas into
code.

Example 6-23. PP4E\System\Media\playfile.py

#!/usr/local/bin/python
"""
##################################################################################
Try to play an arbitrary media file. Allows for specific players instead of
always using general web browser scheme. May not work on your system as is;
audio files use filters and command lines on Unix, and filename associations
on Windows via the start command (i.e., whatever you have on your machine to
run .au files--an audio player, or perhaps a web browser). Configure and
extend as needed. playknownfile assumes you know what sort of media you wish
to open, and playfile tries to determine media type automatically using Python
mimetypes module; both try to launch a web browser with Python webbrowser module
as a last resort when mimetype or platform unknown.
##################################################################################
"""
import os, sys, mimetypes, webbrowser
helpmsg = """
Sorry: can't find a media player for '%s' on your system!
Add an entry for your system to the media player dictionary
for this type of file in playfile.py, or play the file manually.
"""
def trace(*args): print(*args) # with spaces between
##################################################################################
# player techniques: generic and otherwise: extend me
##################################################################################
class MediaTool:
def __init__(self, runtext=''):
self.runtext = runtext
def run(self, mediafile, **options): # most ignore options
fullpath = os.path.abspath(mediafile) # cwd may be anything
self.open(fullpath, **options)
class Filter(MediaTool):
def open(self, mediafile, **ignored):
media = open(mediafile, 'rb')
player = os.popen(self.runtext, 'w') # spawn shell tool
player.write(media.read()) # send to its stdin
class Cmdline(MediaTool):
def open(self, mediafile, **ignored):
cmdline = self.runtext % mediafile # run any cmd line
os.system(cmdline) # use %s for filename
class Winstart(MediaTool): # use Windows registry
def open(self, mediafile, wait=False, **other): # or os.system('start file')
if not wait: # allow wait for curr media
os.startfile(mediafile)
else:
os.system('start /WAIT ' + mediafile)
class Webbrowser(MediaTool):
# file:// requires abs path
def open(self, mediafile, **options):
webbrowser.open_new('file://%s' % mediafile, **options)
##################################################################################
# media- and platform-specific policies: change me, or pass one in
##################################################################################
# map platform to player: change me!
audiotools = {
'sunos5': Filter('/usr/bin/audioplay'), # os.popen().write()
'linux2': Cmdline('cat %s > /dev/audio'), # on zaurus, at least
'sunos4': Filter('/usr/demo/SOUND/play'), # yes, this is that old!
'win32': Winstart() # startfile or system
#'win32': Cmdline('start %s')
}
videotools = {
'linux2': Cmdline('tkcVideo_c700 %s'), # zaurus pda
'win32': Winstart(), # avoid DOS pop up
}
imagetools = {
'linux2': Cmdline('zimager %s'), # zaurus pda
'win32': Winstart(),
}
texttools = {
'linux2': Cmdline('vi %s'), # zaurus pda
'win32': Cmdline('notepad %s') # or try PyEdit?
}
apptools = {
'win32': Winstart() # doc, xls, etc: use at your own risk!
}
# map mimetype of filenames to player tables
mimetable = {'audio': audiotools,
'video': videotools,
'image': imagetools,
'text': texttools, # not html text: browser
'application': apptools}
##################################################################################
# top-level interfaces
##################################################################################
def trywebbrowser(filename, helpmsg=helpmsg, **options):
"""
try to open a file in a web browser
last resort if unknown mimetype or platform, and for text/html
"""
trace('trying browser', filename)
try:
player = Webbrowser() # open in local browser
player.run(filename, **options)
except:
print(helpmsg % filename) # else nothing worked
def playknownfile(filename, playertable={}, **options):
"""
play media file of known type: uses platform-specific
player objects, or spawns a web browser if nothing for
this platform; accepts a media-specific player table
"""
if sys.platform in playertable:
playertable[sys.platform].run(filename, **options) # specific tool
else:
trywebbrowser(filename, **options) # general scheme
def playfile(filename, mimetable=mimetable, **options):
"""
play media file of any type: uses mimetypes to guess media
type and map to platform-specific player tables; spawn web
browser if text/html, media type unknown, or has no table
"""
contenttype, encoding = mimetypes.guess_type(filename) # check name
if contenttype == None or encoding is not None: # can't guess
contenttype = '?/?' # poss .txt.gz
maintype, subtype = contenttype.split('/', 1) # 'image/jpeg'
if maintype == 'text' and subtype == 'html':
trywebbrowser(filename, **options) # special case
elif maintype in mimetable:
playknownfile(filename, mimetable[maintype], **options) # try table
else:
trywebbrowser(filename, **options) # other types
###############################################################################
# self-test code
###############################################################################
if __name__ == '__main__':
# media type known
playknownfile('sousa.au', audiotools, wait=True)
playknownfile('ora-pp3e.gif', imagetools, wait=True)
playknownfile('ora-lp4e.jpg', imagetools)
# media type guessed
input('Stop players and press Enter')
playfile('ora-lp4e.jpg') # image/jpeg
playfile('ora-pp3e.gif') # image/gif
playfile('priorcalendar.html') # text/html
playfile('lp4e-preface-preview.html') # text/html
playfile('lp-code-readme.txt') # text/plain
playfile('spam.doc') # app
playfile('spreadsheet.xls') # app
playfile('sousa.au', wait=True) # audio/basic
input('Done') # stay open if clicked

Although it’s generally possible to open most media files by passing
their names to a web browser these days, this module provides a simple
framework for launching media files with more specific tools, tailored by
both media type and platform. A web browser is used only as a fallback
option, if more specific tools are not available. The net result is an
extendable media file player, which is as specific and portable as the
customizations you provide for its tables.

We’ve seen the program launch tools employed by this script in prior
chapters. The script’s main new concepts have to do with the modules it
uses: the
webbrowser
module to open
some files in a local web browser, as well as the Python
mimetypes
module to determine media type from
file name. Since these are the heart of this code’s matter, let’s explore
these briefly before we run the
script.

The Python webbrowser Module

The standard library
webbrowser
module
used by this example provides a portable interface for
launching web browsers from Python scripts. It attempts to locate a
suitable web browser on your local machine to open a given URL (file or
web address) for display. Its interface is straightforward:

>>> import webbrowser
>>> webbrowser.open_new('file://' +
fullfilename
) # use os.path.abspath()

This code will open the named file in a new web browser window
using whatever browser is found on the underlying computer, or raise an
exception if it cannot. You can tailor the browsers used on your
platform, and the order in which they are attempted, by using the
BROWSER
environment variable and
register
function. By default,
webbrowser
attempts to be
automatically portable across platforms.

Use an argument string of the form “file://...” or “http://...” to
open a file on the local computer or web server, respectively. In fact,
you can pass in any URL that the browser understands. The following pops
up Python’s home page in a new locally-running browser window, for
example:

>>> webbrowser.open_new('http://www.python.org')

Among other things, this is an easy way to display HTML documents
as well as media files, as demonstrated by this section’s example. For
broader applicability, this module can be used as both command-line
script (Python’s
-m
module search
path flag helps here) and as importable tool:

C:\Users\mark\Stuff\Websites\public_html>
python -m webbrowser about-pp.html
C:\Users\mark\Stuff\Websites\public_html>
python -m webbrowser -n about-pp.html
C:\Users\mark\Stuff\Websites\public_html>
python -m webbrowser -t about-pp.html
C:\Users\mark\Stuff\Websites\public_html>
python
>>>
import webbrowser
>>>
webbrowser.open('about-pp.html')
# reuse, new window, new tab
True
>>>
webbrowser.open_new('about-pp.html')
# file:// optional on Windows
True
>>>
webbrowser.open_new_tab('about-pp.html')
True

In both modes, the difference between the three usage forms is
that the first tries to reuse an already-open browser window if
possible, the second tries to open a new window, and the third tries to
open a new tab. In practice, though, their behavior is totally dependent
on what the browser selected on your platform supports, and even on the
platform in general. All three forms may behave the same.

On Windows, for example, all three simply run
os.startfile
by default and thus create a new
tab in an existing window under Internet Explorer 8. This is also why I
didn’t need the “file://” full URL prefix in the preceding listing.
Technically, Internet Explorer is only run if this is what is registered
on your computer for the file type being opened; if not, that file
type’s handler is opened instead. Some images, for example, may open in
a photo viewer instead. On other platforms, such as Unix and Mac OS X,
browser behavior differs, and non-URL file names might not be opened;
use “file://” for
portability
.

We’ll use this module again later in this book. For example, the
PyMailGUI program in
Chapter 14
will employ
it as a way to display HTML-formatted email messages and attachments, as
well as program help. See the Python library manual for more details. In
Chapters
13
and
15
,
we’ll also meet a related call,
urllib.request.urlopen
, which fetches a web
page’s text given a URL, but does not open it in a browser; it may be
parsed, saved, or otherwise
used.

The Python mimetypes Module

To make this
media player module even more useful, we also use the
Python
mimetypes
standard library module to automatically determine the media type from
the filename. We get back a
type/subtype
MIME content-type string if the
type can be determined or
None
if the
guess failed:

>>>
import mimetypes
>>>
mimetypes.guess_type('spam.jpg')
('image/jpeg', None)
>>>
mimetypes.guess_type('TheBrightSideOfLife.mp3')
('audio/mpeg', None)
>>>
mimetypes.guess_type('lifeofbrian.mpg')
('video/mpeg', None)
>>>
mimetypes.guess_type('lifeofbrian.xyz')
# unknown type
(None, None)

Stripping off the first part of the content-type string gives the
file’s general media type, which we can use to select a generic player;
the second part (subtype) can tell us if text is plain or HTML:

>>>
contype, encoding = mimetypes.guess_type('spam.jpg')
>>>
contype.split('/')[0]
'image'
>>>
mimetypes.guess_type('spam.txt')
# subtype is 'plain'
('text/plain', None)
>>>
mimetypes.guess_type('spam.html')
('text/html', None)
>>>
mimetypes.guess_type('spam.html')[0].split('/')[1]
'html'

A subtle thing: the second item in the tuple returned from the
mimetypes
guess is an encoding type
we won’t use here for opening purposes. We still have to pay attention
to it, though—if it is not
None
, it
means the file is compressed (
gzip
or
compress
), even if we receive a media
content type. For example, if the filename is something like
spam.gif.gz
, it’s a compressed image that we don’t
want to try to open directly:

>>>
mimetypes.guess_type('spam.gz')
# content unknown
(None, 'gzip')
>>>
mimetypes.guess_type('spam.gif.gz')
# don't play me!
('image/gif', 'gzip')
>>>
mimetypes.guess_type('spam.zip')
# archives
('application/zip', None)
>>>
mimetypes.guess_type('spam.doc')
# office app files
('application/msword', None)

If the filename you pass in contains a directory path, the path
portion is ignored (only the extension is used). This module is even
smart enough to give us a filename extension for a type—useful if we
need to go the other way, and create a file name from a content
type:

>>>
mimetypes.guess_type(r'C:\songs\sousa.au')
('audio/basic', None)
>>>
mimetypes.guess_extension('audio/basic')
'.au'

Try more calls on your own for more details. We’ll use the
mimetypes
module again in FTP
examples in
Chapter 13
to determine
transfer type (text or binary), and in our email examples in Chapters
13
,
14
, and
16
to
send, save, and open mail attachments.

In
Example 6-23
, we use
mimetypes
to select a table of
platform-specific player commands for the media type of the file to be
played. That is, we pick a player table for the file’s media type, and
then pick a command from the player table for the platform. At both
steps, we give up and run a web browser if there is nothing more
specific to be done.

Using mimetypes guesses for SearchVisitor

To use this
module for directing our text file search scripts we
wrote earlier in this chapter, simply extract the first item in the
content-type returned for a file’s name. For instance, all in the
following list are considered text (except “.pyw”, which we may have
to special-case if we must care):

>>>
for ext in ['.txt', '.py', '.pyw', '.html', '.c', '.h', '.xml']:
...
print(ext, mimetypes.guess_type('spam' + ext))
...
.txt ('text/plain', None)
.py ('text/x-python', None)
.pyw (None, None)
.html ('text/html', None)
.c ('text/plain', None)
.h ('text/plain', None)
.xml ('text/xml', None)

We can add this technique to our earlier
SearchVisitor
class by redefining its
candidate selection method, in order to replace its default extension
lists with
mimetypes
guesses—
yet more evidence of the power
of OOP customization at work:

C:\...\PP4E\Tools>
python
>>>
import mimetypes
>>>
from visitor import SearchVisitor
# or PP4E.Tools.visitor if not .
>>>
>>>
class SearchMimeVisitor(SearchVisitor):
...
def candidate(self, fname):
...
contype, encoding = mimetypes.guess_type(fname)
...
return (contype and
...
contype.split('/')[0] == 'text' and
...
encoding == None)
...
>>>
V = SearchMimeVisitor('mimetypes', trace=0)
# search key
>>>
V.run(r'C:\temp\PP3E\Examples')
# root dir
C:\temp\PP3E\Examples\PP3E\extras\LosAlamosAdvancedClass\day1-system\data.txt ha
s mimetypes
C:\temp\PP3E\Examples\PP3E\Internet\Email\mailtools\mailParser.py has mimetypes
C:\temp\PP3E\Examples\PP3E\Internet\Email\mailtools\mailSender.py has mimetypes
C:\temp\PP3E\Examples\PP3E\Internet\Ftp\mirror\downloadflat.py has mimetypes
C:\temp\PP3E\Examples\PP3E\Internet\Ftp\mirror\downloadflat_modular.py has mimet
ypes
C:\temp\PP3E\Examples\PP3E\Internet\Ftp\mirror\ftptools.py has mimetypes
C:\temp\PP3E\Examples\PP3E\Internet\Ftp\mirror\uploadflat.py has mimetypes
C:\temp\PP3E\Examples\PP3E\System\Media\playfile.py has mimetypes
>>>
V.scount, V.fcount, V.dcount
(8, 1429, 186)

Because this is not completely accurate, though (you may need to
add logic to include extensions like “.pyw” missed by the guess), and
because it’s not even appropriate for all search clients (some may
want to search specific kinds of text only), this scheme was not used
for the original class. Using and tailoring it for your own searches
is left as optional
exercise.

Other books

Ilión by Dan Simmons
Parker by Maddie James
Walking with Ghosts by Baker, John
River of Destiny by Barbara Erskine
Nancy Culpepper by Bobbie Ann Mason
The Rule of Thoughts by James Dashner
Sensei by John Donohue
Moonshine by Thurman, Rob


readsbookonline.com Copyright 2016 - 2024