]> code.delx.au - monosys/blob - mythtv/mythmusic_importm3u.py
160f5f45007a13619b77ae8a82b42c4421dfb7dd
[monosys] / mythtv / mythmusic_importm3u.py
1 #!/usr/bin/env python
2
3 # This script reads in m3u playlists and inserts them into the mythtv database
4
5 # Copyright 2008 James Bunton <jamesbunton@fastmail.fm>
6 # Licensed for distribution under the GPL version 2
7
8 # Copyright 2005 Patrick Riley (http://pathacks.blogspot.com/). You are
9 # free to use and modify this code as you see fit, but it comes with
10 # no warranty whatsoever.
11
12
13 import logging, optparse, os.path, re, sys
14
15 import MySQLdb
16
17 class PlaylistBuilder(object):
18 findPlaylistQuery = """
19 SELECT playlist_id
20 FROM music_playlists
21 WHERE playlist_name = %s
22 """
23 findItemQuery = """
24 SELECT song.song_id
25 FROM music_directories dir JOIN music_songs song ON dir.directory_id = song.directory_id
26 WHERE concat(dir.path, '/', song.filename) = %s
27 """
28 insertPlaylistQuery = """
29 INSERT INTO
30 music_playlists(playlist_name, playlist_songs)
31 VALUES(%s, %s)
32 """
33 updatePlaylistQuery = """
34 UPDATE music_playlists
35 SET playlist_songs = %s
36 WHERE playlist_id = %s
37 """
38
39 def __init__(self, dbHost, dbUser, dbPassword, dbName="mythconverg"):
40 self.dbconnection = MySQLdb.connect(db=dbName, host=dbHost, user=dbUser, passwd=dbPassword)
41 self.cursor = self.dbconnection.cursor()
42
43 def startPlaylist(self, name):
44 self.playlistName = name
45 self.playlistId = -1
46 self.cursor.execute(self.findPlaylistQuery, [self.playlistName])
47 if self.cursor.rowcount < 0:
48 raise ValueError("rowcount < 0?")
49 elif self.cursor.rowcount > 0:
50 assert self.cursor.rowcount == 1, "More than one playlist with the same name?"
51 self.playlistId = self.cursor.fetchone()[0]
52 self.ids = []
53 self.addFile = self.addFileFirst
54
55 def addFileNormal(self, filename):
56 filename = filename.split(os.path.sep, self.strip)[-1]
57 self.cursor.execute(self.findItemQuery, [filename])
58 if self.cursor.rowcount != 1:
59 logging.warning("Did not find an entry for '%s' (return=%d)" % (filename, self.cursor.rowcount))
60 return
61 self.ids.append("%d" % self.cursor.fetchone()[0])
62
63 def addFileFirst(self, filename):
64 self.addFile = self.addFileNormal
65
66 # Pull off path components until we find it
67 self.strip = 0
68 while filename:
69 self.cursor.execute(self.findItemQuery, [filename])
70 if self.cursor.rowcount == 1:
71 break
72 filename = filename.split(os.path.sep, 1)[1]
73 self.strip += 1
74 else:
75 logging.warning("Did not find entry for first file in playlist: '%s' No autodetection of strip amount." % filename)
76 self.strip = 0
77 return
78
79 logging.info("Found file: '%s', auto detected strip amount: %d" % (filename, self.strip))
80 self.ids.append("%d" % self.cursor.fetchone()[0])
81
82 def finishPlaylist(self):
83 idsString = ",".join(self.ids)
84 logging.info("Playlist '%s' is: %s" % (self.playlistName, idsString))
85 if self.playlistId < 0:
86 self.cursor.execute(self.insertPlaylistQuery, [self.playlistName, idsString])
87 else:
88 self.cursor.execute(self.updatePlaylistQuery, [idsString, self.playlistId])
89 # We can't check the rowcount here because update will say 0 if no values actually changed
90 # assert cursor.rowcount == 1, "Insert/update failed? %d" % (playlistId)
91
92
93 def stripComments(it):
94 re_comment = re.compile(r"^\s*#")
95 re_all_whitespace = re.compile(r"^\s*$")
96
97 for line in it:
98 if re_comment.match(line):
99 continue
100 if re_all_whitespace.match(line):
101 continue
102 line = line.strip()
103 yield line
104
105
106 def readMysqlTxt(filename, vars):
107 for line in stripComments(open(filename)):
108 try:
109 key, value = line.split("=")
110 except:
111 logging.warning("Couldn't parse mysql.txt line -- %s" % line)
112 vars[key.lower().strip()] = value.strip()
113
114 def readArgs(vars):
115 parser = optparse.OptionParser(usage="""%prog [options] Playlist.m3u [Another.m3u] ...
116
117 Converts m3u style playlists into playlists for MythTV
118
119 A m3u file is a list of filenames where # begins comments. This script
120 makes one playlist for each m3u file you give it (stripping the extention
121 from the filename to get the playlist name)
122
123 This script works by connecting to your MythTV MySQL database and querying
124 for the filenames found in the .m3u file.
125
126 The database connection settings will be read from the .mythtv/mysql.txt
127 file if possible, otherwise specify them on the command line.
128 """)
129 parser.add_option("--dbhost", help="MythTV database host to connect to",
130 action="store", dest="host", default=vars["dbhostname"])
131 parser.add_option("--dbuser", help="MythTV database username",
132 action="store", dest="user", default=vars["dbusername"])
133 parser.add_option("--dbpassword", help="MythTV database password",
134 action="store", dest="password", default=vars["dbpassword"])
135 parser.add_option("--dbname", help="MythTV database name",
136 action="store", dest="database", default=vars["dbname"])
137 parser.add_option("-v", "--verbose", help="Be verbose",
138 action="store_true", dest="verbose")
139
140 options, filenames = parser.parse_args()
141 vars["dbhostname"] = options.host
142 vars["dbusername"] = options.user
143 vars["dbpassword"] = options.password
144 vars["dbname"] = options.database
145 if options.verbose:
146 logging.basicConfig(level=logging.INFO)
147 else:
148 logging.basicConfig(level=logging.WARNING)
149
150 return filenames
151
152
153 def main():
154 vars = {
155 "dbhostname": "localhost",
156 "dbusername": "mythtv",
157 "dbpassword": "mythtv",
158 "dbname": "mythconverg",
159 }
160
161 found = False
162 for path in ["~/.mythtv/mysql.txt", "~mythtv/.mythtv/mysql.txt", "/etc/mythtv/mysql.txt"]:
163 try:
164 readMysqlTxt(os.path.expanduser(path), vars)
165 found = True
166 break
167 except IOError:
168 continue
169 filenames = readArgs(vars)
170 if not found:
171 logging.warning("Could not read mysql.txt, try running as the mythtv user.")
172
173 builder = PlaylistBuilder(vars["dbhostname"], vars["dbusername"], vars["dbpassword"], vars["dbname"])
174 for filename in filenames:
175 playlistName = os.path.basename(filename)
176 playlistName = playlistName.rsplit(".", 1)[0]
177 logging.info("Processing '%s' as playlist '%s'" % (filename, playlistName))
178
179 baseDir = os.path.dirname(filename)
180 builder.startPlaylist(playlistName)
181 for line in stripComments(open(filename)):
182 line = re.sub("^(\.\./)*", "", line)
183 if not line.startswith("/"):
184 line = os.path.abspath(os.path.join(baseDir, line))
185 builder.addFile(line)
186 builder.finishPlaylist()
187
188
189 if __name__ == '__main__':
190 main()
191