temporary solution to import episodes downloaded by older version
[riffle.git] / sync
blob928d510b8fc9907e1e4dae0d1a6d6820dc345b86
1 #!/usr/bin/env python
2 import gtk, gtk.glade, gobject
3 import os, sys
4 import conf
6 try: import mutagen
7 except ImportError:
8 sys.path.append(os.path.expanduser("~/src/ql/trunk/mutagen"))
9 import mutagen
11 SUPPORTED_EXTENSIONS = '.mp3'
12 # list store columns
13 TITLE=0
14 BACKGROUND=1
15 FULLPATH=2
16 LOCATION=3
17 # locations
18 INCOMING = 0
19 DEVICE = 1
20 # actual paths
21 LOC = {}
22 LOC[INCOMING] = conf.media_dir
23 LOC[DEVICE] = conf.device_dir
26 def list_files(top, ext):
27 if isinstance(ext,list):
28 accept = lambda e: e in ext
29 else:
30 accept = lambda e: e == ext
31 for r,ds,fs in os.walk(top):
32 for f in fs:
33 if accept( os.path.splitext(f)[1] ):
34 yield os.path.join(r,f)
36 def get_device_tracks():
37 return list_files(LOC[DEVICE], SUPPORTED_EXTENSIONS)
39 def get_incoming_tracks():
40 return list_files(LOC[INCOMING], SUPPORTED_EXTENSIONS)
42 class Sync:
43 def __init__(self):
44 self.window = gtk.Window()
45 self.window.set_title('sync')
46 self.window.connect('destroy', gtk.main_quit)
48 vbox = gtk.VBox()
50 scroll = gtk.ScrolledWindow()
51 scroll.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
52 vbox.add(scroll)
54 self.main_list = gtk.TreeView()
55 self.main_list.set_headers_visible(False)
56 self.main_list.set_reorderable(True)
57 self.main_list.connect("key-press-event", self.on_key_press)
58 self.main_list.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
60 text_renderer = gtk.CellRendererText()
61 column = gtk.TreeViewColumn("track", text_renderer)
62 column.add_attribute(text_renderer, "text", TITLE)
63 column.add_attribute(text_renderer, "background", BACKGROUND)
65 self.main_list.append_column( column )
66 scroll.add(self.main_list)
68 go_button = gtk.Button('Go!')
69 go_button.connect("clicked", self.do_sync)
70 vbox.add_with_properties(go_button,'expand',False)
72 self.window.add(vbox)
74 self.window.resize(400,600)
75 self.window.show_all()
77 def add_entries(self, lst):
78 if self.main_list.get_model() is None:
79 self.main_list.set_model( gtk.ListStore(str,str,str,int) )
80 for i in lst:
81 self.main_list.get_model().append( i )
83 def do_sync(self,*argv):
84 to_copy = [row for row in self.main_list.get_model() if row[LOCATION] == INCOMING]
85 CopyProgress(to_copy, LOC[DEVICE])
87 def on_key_press(self,widget,event,*argv):
88 if event.keyval in (gtk.keysyms.Delete , gtk.keysyms.KP_Delete):
89 self.delete_selected()
91 def want_file_gone(self, title):
92 print "Do you also want to delete track '%s' from incoming directory?" % title
93 return False
95 def delete_selected(self):
96 model,indexes = self.main_list.get_selection().get_selected_rows()
97 iters = [model.get_iter(i) for i in indexes]
98 for i in iters:
99 if (model[i][LOCATION] != INCOMING) or self.want_file_gone(model[i][TITLE]):
100 os.remove(model[i][FULLPATH])
101 model.remove( i )
103 class CopyProgress:
104 xml = gtk.glade.XML('sync.glade', root='copy_progress_dialog')
105 dialog = xml.get_widget('copy_progress_dialog')
106 total_progress = xml.get_widget('total_progress')
107 file_progress = xml.get_widget('file_progress')
108 dialog.hide()
110 buf_len = 128 * 1024
112 def __init__(self, sources, dest):
113 if len(sources) == 0: return
114 self.total_progress.set_fraction(0)
115 self.file_progress.set_fraction(0)
116 self.dialog.show_all()
118 self.sources = sources
119 self.total_sources_count = len(sources)
120 self.dest = dest
121 self.source_file = None
122 self.dest_file = None
124 gobject.idle_add(self.copy_task)
126 def copy_task(self):
127 if self.source_file is None:
128 if len(self.sources) == 0:
129 self.dialog.hide() # done
130 return False
131 self.row = self.sources[0]
132 self.source = self.row[FULLPATH]
133 self.sources = self.sources[1:]
134 self.source_file = open(self.source, 'r')
135 self.new_path = os.path.join( self.dest, os.path.basename( self.source ))
136 self.dest_file = open(self.new_path, 'w')
137 self.current_offset = 0
138 self.current_size = os.stat( self.source ).st_size
139 self.file_progress.set_fraction( 0.0 )
140 chunk_size = min( self.buf_len, self.current_size - self.current_offset )
141 self.dest_file.write( self.source_file.read( chunk_size ))
142 self.current_offset += chunk_size
143 self.file_progress.set_fraction( float(self.current_offset) / self.current_size )
144 if self.current_offset == self.current_size:
145 self.dest_file.close()
146 self.dest_file = None
147 self.source_file.close()
148 self.source_file = None
149 os.remove( self.source )
150 self.total_progress.set_fraction( 1.0 - (float(len(self.sources)) / self.total_sources_count))
151 self.row[BACKGROUND] = '#d0d0f0'
152 self.row[LOCATION] = DEVICE
153 self.row[FULLPATH] = self.new_path
154 return True
157 def track_title(t):
158 try:
159 tags = mutagen.File(t).tags
160 if 'TALB' in tags:
161 return "%s (%s)" % (tags['TIT2'], tags['TALB'])
162 else:
163 return tags['TIT2']
164 except:
165 return os.path.basename(t)
167 try:
168 LOC[DEVICE] = sys.argv[1]
169 LOC[INCOMING] = sys.argv[2]
170 except IndexError:
171 pass
173 app = Sync()
174 for entry in [(track_title(t),'#d0d0f0',t,DEVICE) for t in get_device_tracks()]:
175 app.add_entries([entry])
176 for entry in [(track_title(t),'#90ff90',t,INCOMING) for t in get_incoming_tracks()]:
177 app.add_entries([entry])
178 gtk.main()