~ubuntu-branches/ubuntu/feisty/soundconverter/feisty

« back to all changes in this revision

Viewing changes to src/soundconverter.py

  • Committer: Bazaar Package Importer
  • Author(s): William Alexander Grant
  • Date: 2006-09-25 07:25:55 UTC
  • mfrom: (1.1.4 upstream)
  • Revision ID: james.westby@ubuntu.com-20060925072555-x4bd5pj4y6dhl86d
Tags: 0.9.1-0ubuntu1
* Preemptive fake sync from Sid. This is actually 0.9.1-1.
* New upstream release.
  - Change gstreamer0.8* dependencies to the equivalent gstreamer0.10 ones
    (Closes: #380473).
  - Drop the Debian .desktop, as there's now one included upstream.
  - Use CDBS, as the new upstream uses autotools.
  - Add python-gtk2 to Build-Depends-Indep, due to autotools detection.
* Bumped debhelper dependency to >= 5.
* New maintainer.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
# -*- coding: utf-8 -*-
 
3
#
 
4
# SoundConverter - GNOME application for converting between audio formats. 
 
5
# Copyright 2004 Lars Wirzenius
 
6
# Copyright 2005-2006 Gautier Portet
 
7
#
 
8
# This program is free software; you can redistribute it and/or modify
 
9
# it under the terms of the GNU General Public License as published by
 
10
# the Free Software Foundation; version 2 of the License.
 
11
#
 
12
# This program is distributed in the hope that it will be useful, but
 
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 
15
# General Public License for more details.
 
16
#
 
17
# You should have received a copy of the GNU General Public License
 
18
# along with this program; if not, write to the Free Software
 
19
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 
20
# USA
 
21
 
 
22
NAME = "SoundConverter"
 
23
VERSION = "@version@"
 
24
GLADE = "@datadir@/soundconverter/soundconverter.glade"
 
25
 
 
26
if "datadir" in GLADE:
 
27
        GLADE = "./data"
 
28
 
 
29
print "%s %s" % (NAME, VERSION)
 
30
 
 
31
# Python standard stuff.
 
32
import sys
 
33
import os
 
34
import inspect
 
35
import getopt
 
36
import textwrap
 
37
import urlparse
 
38
import string
 
39
import thread
 
40
import urllib
 
41
import time
 
42
 
 
43
# GNOME and related stuff.
 
44
import pygtk
 
45
pygtk.require("2.0")
 
46
import gtk
 
47
import gtk.glade
 
48
import gnome
 
49
import gnome.ui
 
50
import gconf
 
51
import gobject
 
52
gobject.threads_init()
 
53
 
 
54
try:
 
55
        # gnome.vfs is deprecated
 
56
        import gnomevfs
 
57
except ImportError:
 
58
        import gnome.vfs
 
59
        gnomevfs = gnome.vfs
 
60
 
 
61
# GStreamer
 
62
try:
 
63
        # 0.10
 
64
        import pygst
 
65
        pygst.require('0.10')
 
66
        import gst
 
67
except ImportError:
 
68
        # 0.8
 
69
        import gst
 
70
        pygst = None
 
71
print "  using Gstreamer version: %s, Python binding version: %s" % (
 
72
                ".".join([str(s) for s in gst.gst_version]), 
 
73
                ".".join([str(s) for s in gst.pygst_version]) )
 
74
 
 
75
# This is missing from gst, for some reason.
 
76
FORMAT_PERCENT_SCALE = 10000
 
77
 
 
78
#localization
 
79
import locale
 
80
import gettext
 
81
PACKAGE = "soundconverter"
 
82
gettext.bindtextdomain(PACKAGE,"@datadir@/locale")
 
83
locale.setlocale(locale.LC_ALL,"")
 
84
gettext.textdomain(PACKAGE)
 
85
gettext.install(PACKAGE,localedir=None,unicode=1)
 
86
 
 
87
gtk.glade.bindtextdomain(PACKAGE,"@datadir@/locale")
 
88
gtk.glade.textdomain(PACKAGE)
 
89
 
 
90
TRANSLATORS = ("""
 
91
Guillaume Bedot <guillaume.bedot wanadoo.fr> (French)
 
92
Dominik Zabłotny <dominz wp.pl> (Polish) 
 
93
Jonh Wendell <wendell bani.com.br> (Portuguese Brazilian)
 
94
Marc E. <m4rccd yahoo.com> (Spanish)
 
95
""")
 
96
 
 
97
# Names of columns in the file list
 
98
VISIBLE_COLUMNS = ["filename"]
 
99
ALL_COLUMNS = VISIBLE_COLUMNS + ["META"] 
 
100
 
 
101
MP3_CBR, MP3_ABR, MP3_VBR = range(3)
 
102
 
 
103
# add here any format you want to be read
 
104
mime_whitelist = (
 
105
        "audio/", 
 
106
        "video/", 
 
107
        "application/ogg", 
 
108
        "application/x-id3",
 
109
        "application/x-ape",
 
110
        "application/vnd.rn-realmedia",
 
111
        "application/x-shockwave-flash",
 
112
)
 
113
 
 
114
# add here the formats not containing tags 
 
115
# not to bother searching in them
 
116
tag_blacklist = (
 
117
        "audio/x-wav",
 
118
)
 
119
 
 
120
 
 
121
# Name and pattern for CustomFileChooser
 
122
filepattern = (
 
123
        ("All files","*.*"),
 
124
        ("MPEG 1 Layer 3 files","*.mp3"),
 
125
        ("Ogg Vorbis files","*.ogg"),
 
126
        ("iTunes AAC files","*.m4a"),
 
127
        ("WAVEform audio format","*.wav"),
 
128
        ("Advanced Audio Coding","*.aac"),
 
129
        ("Free Lossless Audio Codec","*.flac"),
 
130
        ("Audio Codec 3","*.ac3")
 
131
)
 
132
 
 
133
def vfs_walk(uri):
 
134
        """similar to os.path.walk, but with gnomevfs.
 
135
        
 
136
        uri -- the base folder uri.
 
137
        return a list of uri.
 
138
        
 
139
        """
 
140
        if str(uri)[-1] != '/':
 
141
                uri = uri.append_string("/")
 
142
 
 
143
        filelist = []   
 
144
 
 
145
        try:
 
146
                dirlist = gnomevfs.open_directory(uri)
 
147
        except:
 
148
                pass
 
149
                log(_("skipping: '%s'") % uri)
 
150
                return filelist
 
151
                
 
152
        for file_info in dirlist:
 
153
                if file_info.name[0] == ".":
 
154
                        continue
 
155
 
 
156
                if file_info.type == gnomevfs.FILE_TYPE_DIRECTORY:
 
157
                        filelist.extend(
 
158
                                vfs_walk(uri.append_path(file_info.name)) )
 
159
 
 
160
                if file_info.type == gnomevfs.FILE_TYPE_REGULAR:
 
161
                        filelist.append( str(uri.append_file_name(file_info.name)) )
 
162
        return filelist
 
163
 
 
164
def vfs_makedirs(path_to_create):
 
165
        """Similar to os.makedirs, but with gnomevfs"""
 
166
        
 
167
        uri = gnomevfs.URI(path_to_create)
 
168
        path = uri.path
 
169
 
 
170
        # start at root
 
171
        uri =  uri.resolve_relative("/")
 
172
        
 
173
        for folder in path.split("/"):
 
174
                if not folder:
 
175
                        continue
 
176
                uri = uri.append_string(folder)
 
177
                try:
 
178
                        gnomevfs.make_directory(uri, 0777)
 
179
                except gnomevfs.FileExistsError:
 
180
                        pass
 
181
                except :
 
182
                        return False
 
183
        return True     
 
184
 
 
185
def vfs_unlink(filename):
 
186
        gnomevfs.unlink(gnomevfs.URI(filename))
 
187
 
 
188
def vfs_exists(filename):
 
189
        return gnomevfs.exists(filename)
 
190
 
 
191
# GStreamer gnomevfssrc helpers
 
192
 
 
193
def vfs_encode_filename(filename):
 
194
        return filename
 
195
 
 
196
def file_encode_filename(filename):
 
197
        filename = gnomevfs.get_local_path_from_uri(filename)
 
198
        filename = filename.replace(" ", "\ ");
 
199
        return filename
 
200
        
 
201
use_gnomevfs = False
 
202
 
 
203
def markup_escape(str):
 
204
    str = "&amp;".join(str.split("&"))
 
205
    str = "&lt;".join(str.split("<"))
 
206
    str = "&gt;".join(str.split(">"))
 
207
    return str
 
208
 
 
209
def filename_escape(str):
 
210
    str = str.replace("'","\'")
 
211
    str = str.replace("\"","\\\"")
 
212
    str = str.replace("!","\!")
 
213
    return str
 
214
 
 
215
if gst.element_factory_find("gnomevfssrc"):
 
216
        gstreamer_source = "gnomevfssrc"
 
217
        gstreamer_sink = "gnomevfssink"
 
218
        encode_filename = vfs_encode_filename
 
219
        use_gnomevfs = True
 
220
        print "  using gnomevfssrc"
 
221
else:
 
222
        gstreamer_source = "filesrc"
 
223
        gstreamer_sink = "filesink"
 
224
        encode_filename = file_encode_filename
 
225
        print "  NOT using gnomevfssrc, look for a gnomevfs gstreamer package."
 
226
 
 
227
 
 
228
# logging & debugging  
 
229
 
 
230
def log(*args):
 
231
        if get_option("quiet") == False:
 
232
                print " ".join([str(msg) for msg in args])
 
233
 
 
234
def debug(*args):
 
235
        if get_option("debug") == True:
 
236
                print " ".join([str(msg) for msg in args])
 
237
 
 
238
def gtk_sleep(duration):
 
239
        start = time.time()
 
240
        while time.time() < start + duration:
 
241
                while gtk.events_pending():
 
242
                        gtk.main_iteration(False)
 
243
                time.sleep(0.010)
 
244
 
 
245
 
 
246
def UNUSED_display_from_mime(mime):
 
247
        # TODO
 
248
        mime_dict = {
 
249
                "application/ogg": "Ogg Vorbis",
 
250
                "audio/x-wav": "MS WAV",
 
251
                "audio/mpeg": "MPEG 1 Layer 3 (MP3)",
 
252
                "audio/x-flac": "FLAC",
 
253
                "audio/x-musepack": "MusePack",
 
254
                "audio/x-au": "AU",
 
255
        }
 
256
        return mime_dict[mime]
 
257
 
 
258
 
 
259
class SoundConverterException(Exception):
 
260
 
 
261
        def __init__(self, primary, secondary):
 
262
                Exception.__init__(self)
 
263
                self.primary = primary
 
264
                self.secondary = secondary
 
265
                
 
266
 
 
267
def filename_to_uri(filename):
 
268
        return "file://" + urllib.quote(os.path.abspath(filename))
 
269
 
 
270
 
 
271
class SoundFile:
 
272
 
 
273
        """Meta data information about a sound file (uri, tags)."""
 
274
 
 
275
        #def __init__(self, base_path, filename=None):
 
276
        def __init__(self, uri, base_path=None):
 
277
 
 
278
                self.uri = uri
 
279
                if base_path:
 
280
                        self.base_path = base_path
 
281
                        self.filename = uri[len(base_path):]
 
282
                else:
 
283
                        self.base_path, self.filename = os.path.split(self.uri)
 
284
                        self.base_path += "/"
 
285
                self.filename_for_display = gnomevfs.unescape_string_for_display(self.filename)
 
286
        
 
287
                self.tags = {
 
288
                        "track-number": 0,
 
289
                        "title":  "Unknown Title",
 
290
                        "artist": "Unknown Artist",
 
291
                        "album":  "Unknown Album",
 
292
                }
 
293
                self.have_tags = False
 
294
                self.tags_read = False
 
295
                self.duration = 0       
 
296
                self.mime_type = None 
 
297
          
 
298
        def get_uri(self):
 
299
                return self.uri
 
300
                
 
301
        def get_base_path(self):
 
302
                return self.base_path
 
303
                
 
304
        def get_filename(self):
 
305
                return self.filename
 
306
                
 
307
        def get_filename_for_display(self):
 
308
                return self.filename_for_display
 
309
                
 
310
        def add_tags(self, taglist):
 
311
                for key in taglist.keys():
 
312
                        self.tags[key] = taglist[key]
 
313
                        
 
314
        def get_tag_names(self):
 
315
                return self.tags.key()
 
316
                        
 
317
        def get_tag(self, key, default=""):
 
318
                return self.tags.get(key, default)
 
319
 
 
320
        get = get_tag
 
321
        __getitem__ = get_tag
 
322
 
 
323
        def keys(self):
 
324
                return self.tags.keys()
 
325
 
 
326
 
 
327
class TargetNameCreationFailure(SoundConverterException):
 
328
 
 
329
        """Exception thrown when TargetNameGenerator can't create name."""
 
330
 
 
331
        def __init__(self, name):
 
332
                SoundConverterException.__init__(self, _("File exists."),
 
333
                                                                                 _("The file %s exists already"))
 
334
 
 
335
class TargetNameGenerator:
 
336
 
 
337
        """Generator for creating the target name from an input name."""
 
338
 
 
339
        nice_chars = string.ascii_letters + string.digits + ".-_/"
 
340
 
 
341
        def __init__(self):
 
342
                self.folder = None
 
343
                self.subfolders = ""
 
344
                self.basename= "%(.inputname)s"
 
345
                self.suffix = None
 
346
                self.replace_messy_chars = False
 
347
                self.max_tries = 2
 
348
                if use_gnomevfs:
 
349
                        self.exists = gnomevfs.exists
 
350
                else:
 
351
                        self.exists = os.path.exists
 
352
 
 
353
        # This is useful for unit testing.                
 
354
        def set_exists(self, exists):
 
355
                self.exists = exists
 
356
 
 
357
        def set_target_suffix(self, suffix):
 
358
                self.suffix = suffix
 
359
                
 
360
        def set_folder(self, folder):
 
361
                self.folder = folder
 
362
                
 
363
        def set_subfolder_pattern(self, pattern):
 
364
                self.subfolders = pattern
 
365
                
 
366
        def set_basename_pattern(self, pattern):
 
367
                self.basename = pattern
 
368
                
 
369
        def set_replace_messy_chars(self, yes_or_no):
 
370
                self.replace_messy_chars = yes_or_no
 
371
                
 
372
        def get_target_name(self, sound_file):
 
373
 
 
374
                u = gnomevfs.URI(sound_file.get_uri())
 
375
                root, ext = os.path.splitext(u.path)
 
376
                if u.host_port:
 
377
                        host = "%s:%s" % (u.host_name, u.host_port)
 
378
                else:
 
379
                        host = u.host_name
 
380
 
 
381
                root = urlparse.urlsplit(sound_file.get_base_path())[2]
 
382
                basename, ext = os.path.splitext(urllib.unquote(sound_file.get_filename()))
 
383
                
 
384
                dict = {
 
385
                        ".inputname": basename,
 
386
                        "album": "",
 
387
                        "artist": "",
 
388
                        "title": "",
 
389
                        "track-number": 0,
 
390
                }
 
391
                for key in sound_file.keys():
 
392
                        dict[key] = sound_file[key]
 
393
                
 
394
                pattern = os.path.join(self.subfolders, self.basename + self.suffix)
 
395
                result = urllib.quote(pattern % dict)
 
396
                if self.replace_messy_chars:
 
397
                        s = ""
 
398
                        result = urllib.unquote(result)
 
399
                        for c in result:
 
400
                                if c not in self.nice_chars:
 
401
                                        s += "_"
 
402
                                else:
 
403
                                        s += c
 
404
                        result = urllib.quote(s)
 
405
 
 
406
                if self.folder is None:
 
407
                        folder = root
 
408
                else:
 
409
                        folder = self.folder
 
410
                result = os.path.join(folder, result)
 
411
 
 
412
                tuple = (u.scheme, host, result, "", u.fragment_identifier)
 
413
                u2 = urlparse.urlunsplit(tuple)
 
414
                #TODO:
 
415
                #if self.exists(u2):
 
416
                #       vfs_unlink(u2)
 
417
                #       raise TargetNameCreationFailure(u2)
 
418
                return u2
 
419
 
 
420
 
 
421
class ErrorDialog:
 
422
 
 
423
        def __init__(self, glade):
 
424
                self.dialog = glade.get_widget("error_dialog")
 
425
                self.primary = glade.get_widget("primary_error_label")
 
426
                self.secondary = glade.get_widget("secondary_error_label")
 
427
                
 
428
        def show(self, primary, secondary):
 
429
                self.primary.set_markup(primary)
 
430
                self.secondary.set_markup(secondary)
 
431
                self.dialog.run()
 
432
                self.dialog.hide()
 
433
 
 
434
        def show_exception(self, exception):
 
435
                self.show("<b>%s</b>" % markup_escape(exception.primary),
 
436
                                  exception.secondary)
 
437
 
 
438
 
 
439
class ErrorPrinter:
 
440
 
 
441
        def show(self, primary, secondary):
 
442
                sys.stderr.write(_("\n\nError: %s\n%s\n") % (primary, secondary))
 
443
                sys.exit(1)
 
444
 
 
445
        def show_exception(self, e):
 
446
                self.show(e.primary, e.secondary)
 
447
 
 
448
 
 
449
error = None
 
450
 
 
451
_thread_sleep = 0.1
 
452
#_thread_method = "thread"
 
453
#_thread_method = "idle"
 
454
_thread_method = "timer"
 
455
 
 
456
class BackgroundTask:
 
457
 
 
458
        """A background task.
 
459
        
 
460
        To use: derive a subclass and define the methods setup, work, and
 
461
        finish. Then call the run method when you want to start the task.
 
462
        Call the stop method if you want to stop the task before it finishes
 
463
        normally."""
 
464
 
 
465
        def __init__(self):
 
466
                self.paused = False
 
467
                self.current_paused_time = 0
 
468
 
 
469
        def run(self):
 
470
                """Start running the task. Call setup()."""
 
471
                try:
 
472
                        self.setup()
 
473
                except SoundConverterException, e:
 
474
                        error.show_exception(e)
 
475
                        return
 
476
                self.paused = False
 
477
                self.run_start_time = time.time()
 
478
                self.current_paused_time = 0
 
479
                self.paused_time = 0
 
480
 
 
481
                if _thread_method == "timer":
 
482
                        self.id = gobject.timeout_add( int(_thread_sleep*1000), self.do_work) 
 
483
                elif _thread_method == "idle":
 
484
                        self.id = gobject.idle_add(self.do_work)
 
485
                else:
 
486
                        thread.start_new_thread(self.thread_work, ())
 
487
 
 
488
        def thread_work(self):
 
489
                working = True
 
490
                while self and working:
 
491
                        #gtk.threads_enter()
 
492
                        working = self.do_work_()
 
493
                        #gtk.threads_leave()
 
494
                        sleep(_thread_sleep)
 
495
                        while gtk.events_pending():
 
496
                                gtk.main_iteration()
 
497
 
 
498
 
 
499
        def do_work(self):
 
500
                #gtk.threads_enter()
 
501
                working = self.do_work_()
 
502
                #gtk.threads_leave()
 
503
                return working
 
504
 
 
505
 
 
506
        def do_work_(self):
 
507
                """Do some work by calling work(). Call finish() if work is done."""
 
508
                try:
 
509
                        if _thread_method == "idle":
 
510
                                time.sleep(_thread_sleep)
 
511
                        if self.paused:
 
512
                                if not self.current_paused_time:
 
513
                                        self.current_paused_time = time.time()
 
514
                                return True
 
515
                        else:
 
516
                                if self.current_paused_time:
 
517
                                        self.paused_time += time.time() - self.current_paused_time
 
518
                                        self.current_paused_time = 0
 
519
                                        
 
520
                        if self.work():
 
521
                                return True
 
522
                        else:
 
523
                                self.run_finish_time = time.time()
 
524
                                self.finish()
 
525
                                self._run = False
 
526
                                self = None
 
527
                                return False
 
528
                except SoundConverterException, e:
 
529
                        self._run = False
 
530
                        error.show_exception(e)
 
531
                        return False
 
532
 
 
533
        def stop(self):
 
534
                """Stop task processing. Finish() is not called."""
 
535
                if 'id' in dir(self) and self.id is not None:
 
536
                        gobject.source_remove(self.id)
 
537
                        self.id = None
 
538
 
 
539
        def setup(self):
 
540
                """Set up the task so it can start running."""
 
541
                pass
 
542
                
 
543
        def work(self):
 
544
                """Do some work. Return False if done, True if more work to do."""
 
545
                return False
 
546
                
 
547
        def finish(self):
 
548
                """Clean up the task after all work has been done."""
 
549
                pass
 
550
 
 
551
 
 
552
class TaskQueue(BackgroundTask):
 
553
 
 
554
        """A queue of tasks.
 
555
        
 
556
        A task queue is a queue of other tasks. If you need, for example, to
 
557
        do simple tasks A, B, and C, you can create a TaskQueue and add the
 
558
        simple tasks to it:
 
559
        
 
560
                q = TaskQueue()
 
561
                q.add(A)
 
562
                q.add(B)
 
563
                q.add(C)
 
564
                q.run()
 
565
                
 
566
        The task queue behaves as a single task. It will execute the
 
567
        tasks in order and start the next one when the previous finishes."""
 
568
 
 
569
        def __init__(self):
 
570
                BackgroundTask.__init__(self)
 
571
                self.tasks = []
 
572
                self.running = False
 
573
                self.tasks_number=0
 
574
                self.tasks_current=0
 
575
 
 
576
        def is_running(self):
 
577
                return self.running
 
578
 
 
579
        def add(self, task):
 
580
                self.tasks.append(task)
 
581
                self.tasks_number += 1
 
582
                
 
583
        def get_current_task(self):
 
584
                if self.tasks:
 
585
                        return self.tasks[0]
 
586
                else:
 
587
                        return None
 
588
 
 
589
        def setup(self):
 
590
                """ BackgroundTask setup callback """
 
591
                self.running = True
 
592
                self.start_time = time.time()
 
593
 
 
594
                if self.tasks:
 
595
                        self.tasks[0].setup()
 
596
                        self.setup_hook(self.tasks[0])
 
597
 
 
598
                        
 
599
        def work(self):
 
600
                """ BackgroundTask work callback """
 
601
                if self.tasks:
 
602
                        ret = self.tasks[0].work()
 
603
                        self.work_hook(self.tasks[0])
 
604
                        if not ret:
 
605
                                self.tasks[0].finish()
 
606
                                self.finish_hook(self.tasks[0])
 
607
                                self.tasks = self.tasks[1:]
 
608
                                if self.tasks:
 
609
                                        self.tasks_current += 1
 
610
                                        self.tasks[0].setup()
 
611
                return len(self.tasks) > 0
 
612
 
 
613
        def finish(self):
 
614
                """ BackgroundTask finish callback """
 
615
                self.running = False
 
616
                log("Queue done in %ds" % (time.time() - self.start_time))
 
617
                self.queue_ended()
 
618
 
 
619
        def stop(self):
 
620
                if self.tasks:
 
621
                        self.tasks[0].stop()
 
622
                BackgroundTask.stop(self)
 
623
                self.running = False
 
624
                self.tasks = []
 
625
 
 
626
        # The following hooks are called after each sub-task has been set up,
 
627
        # after its work method has been called, and after it has finished.
 
628
        # Subclasses may override these to provide additional processing.
 
629
 
 
630
        def setup_hook(self, task):
 
631
                pass
 
632
                
 
633
        def work_hook(self, task):
 
634
                pass
 
635
                
 
636
        def finish_hook(self, task):
 
637
                pass
 
638
 
 
639
        # The following is called when the Queue is finished
 
640
        def queue_ended(self):
 
641
                pass
 
642
 
 
643
 
 
644
class NoLink(SoundConverterException):
 
645
        
 
646
        def __init__(self):
 
647
                SoundConverterException.__init__(self, _("Internal error"),
 
648
                                                                _("Couldn't link GStreamer elements.\n Please report this as a bug."))
 
649
 
 
650
class UnknownType(SoundConverterException):
 
651
        
 
652
        def __init__(self, uri, mime_type):
 
653
                SoundConverterException.__init__(self, _("Unknown type %s") % mime_type,
 
654
                                                                (_("The file %s is of an unknown type.\n Please ask the developers to add support\n for files of this type if it is important\n to you.")) % uri)
 
655
 
 
656
 
 
657
class Pipeline(BackgroundTask):
 
658
 
 
659
        """A background task for running a GstPipeline."""
 
660
 
 
661
        def __init__(self):
 
662
                BackgroundTask.__init__(self)
 
663
                self.pipeline = None #gst.Pipeline()
 
664
                self.command = ""
 
665
                self.parsed = False
 
666
                self.signals = []
 
667
                self.processing = False
 
668
                self.eos = False
 
669
                
 
670
        def setup(self):
 
671
                #print "Pipeline.setup()"
 
672
                self.play()
 
673
        
 
674
        def work(self):
 
675
                #if self.pipeline.get_state() == gst.STATE_NULL:
 
676
                #       log("error: pipeline.state == null")
 
677
                        #return False
 
678
                #print "work:", self
 
679
                #time.sleep(0.01)
 
680
                if self.eos:
 
681
                        #print "  got eos:", self.sound_file.get_filename_for_display()
 
682
                        return False
 
683
                return True
 
684
 
 
685
        def finish(self):
 
686
                #print "Pipeline.finish()"
 
687
                self.stop_pipeline()
 
688
 
 
689
        def add_command(self, command):
 
690
                if self.command:
 
691
                        self.command += " ! "
 
692
                self.command += command
 
693
 
 
694
        def add_signal(self, name, signal, callback):
 
695
                self.signals.append( (name, signal, callback,) )
 
696
 
 
697
        def toggle_pause(self, paused):
 
698
                if paused:
 
699
                        self.pipeline.set_state(gst.STATE_PAUSED)
 
700
                else:
 
701
                        self.pipeline.set_state(gst.STATE_PLAYING)
 
702
 
 
703
        def found_tag(self, decoder, something, taglist):
 
704
                pass
 
705
 
 
706
        def on_message(self, bus, message):
 
707
                t = message.type
 
708
                if t == gst.MESSAGE_ERROR:
 
709
                        err, debug = message.parse_error()
 
710
                        self.eos = True
 
711
                        log("error:%s (%s)" % (err, self.sound_file.get_filename_for_display()))
 
712
                elif t == gst.MESSAGE_EOS:
 
713
                        self.eos = True
 
714
                if message.type.value_nicks[1] == "tag":
 
715
                        self.found_tag(self, "", message.parse_tag())   
 
716
                return True
 
717
 
 
718
        def play(self):
 
719
                if not self.parsed:
 
720
                        debug("launching: '%s'" % self.command)
 
721
                        self.pipeline = gst.parse_launch(self.command)
 
722
                        for name, signal, callback in self.signals:
 
723
                                self.pipeline.get_by_name(name).connect(signal,callback)
 
724
                        self.parsed = True
 
725
        
 
726
                bus = self.pipeline.get_bus()
 
727
                bus.add_signal_watch()
 
728
                watch_id = bus.connect('message', self.on_message)
 
729
                self.watch_id = watch_id
 
730
        
 
731
                self.pipeline.set_state(gst.STATE_PLAYING)
 
732
 
 
733
        def stop_pipeline(self):
 
734
                if not self.pipeline:
 
735
                        log("pipeline already stopped!")
 
736
                        return
 
737
                bus = self.pipeline.get_bus()
 
738
                bus.disconnect(self.watch_id)
 
739
                bus.remove_signal_watch()
 
740
                self.pipeline.set_state(gst.STATE_NULL)
 
741
                self.pipeline = None
 
742
                del self.watch_id
 
743
 
 
744
        def get_position(self):
 
745
                return 0
 
746
 
 
747
class TypeFinder(Pipeline):
 
748
        def __init__(self, sound_file):
 
749
                Pipeline.__init__(self)
 
750
                self.sound_file = sound_file
 
751
        
 
752
                command = '%s location="%s" ! typefind name=typefinder ! fakesink' % \
 
753
                        (gstreamer_source, encode_filename(self.sound_file.get_uri()))
 
754
                self.add_command(command)
 
755
                self.add_signal("typefinder", "have-type", self.have_type)
 
756
 
 
757
        def set_found_type_hook(self, found_type_hook):
 
758
                self.found_type_hook = found_type_hook
 
759
        
 
760
        def have_type(self, typefind, probability, caps):
 
761
                mime_type = caps.to_string()
 
762
                #debug("have_type:", mime_type, self.sound_file.get_filename_for_display())
 
763
                self.sound_file.mime_type = None
 
764
                for t in mime_whitelist:
 
765
                        if t in mime_type:
 
766
                                self.sound_file.mime_type = mime_type
 
767
                if not self.sound_file.mime_type:
 
768
                        log("Mime type skipped: %s (mail us if this is an error)" % mime_type)
 
769
        
 
770
        def work(self):
 
771
                return Pipeline.work(self) and not self.sound_file.mime_type
 
772
 
 
773
        def finish(self):
 
774
                Pipeline.finish(self)
 
775
                if self.found_type_hook and self.sound_file.mime_type:
 
776
                        gobject.idle_add(self.found_type_hook, self.sound_file, self.sound_file.mime_type)
 
777
 
 
778
 
 
779
class Decoder(Pipeline):
 
780
 
 
781
        """A GstPipeline background task that decodes data and finds tags."""
 
782
 
 
783
        def __init__(self, sound_file):
 
784
                #print "Decoder()"
 
785
                Pipeline.__init__(self)
 
786
                self.sound_file = sound_file
 
787
                self.time = 0
 
788
                self.position = 0
 
789
                
 
790
                command = '%s location="%s" name=src ! decodebin name=decoder' % \
 
791
                        (gstreamer_source, encode_filename(self.sound_file.get_uri()))
 
792
                self.add_command(command)
 
793
                self.add_signal("decoder", "new-decoded-pad", self.new_decoded_pad)
 
794
 
 
795
                # TODO add error management
 
796
 
 
797
        def have_type(self, typefind, probability, caps):
 
798
                pass
 
799
 
 
800
        def query_duration(self):
 
801
                try:
 
802
                        if not self.sound_file.duration:
 
803
                                self.sound_file.duration = self.pipeline.query_duration(gst.FORMAT_TIME)[0] / gst.SECOND
 
804
                                debug("got file duration:", self.sound_file.duration)
 
805
                except gst.QueryError:
 
806
                        pass
 
807
 
 
808
        def found_tag(self, decoder, something, taglist):
 
809
                pass
 
810
 
 
811
        def _buffer_probe(self, pad, buffer):
 
812
                """buffer probe callback used to get real time since the beginning of the stream"""
 
813
                if time.time() > self.time + 0.1:
 
814
                        self.time = time.time()
 
815
                        self.position = float(buffer.timestamp) / gst.SECOND
 
816
 
 
817
                if buffer.timestamp == gst.CLOCK_TIME_NONE:
 
818
                        debug("removing probe")
 
819
                        pad.remove_buffer_probe(self.probe_id)
 
820
                return True
 
821
        
 
822
        def new_decoded_pad(self, decoder, pad, is_last):
 
823
                """ called when a decoded pad is created """
 
824
                self.probe_id = pad.add_buffer_probe(self._buffer_probe)
 
825
                self.processing = True
 
826
                self.query_duration()
 
827
                #self.sound_file.duration = self.pipeline.query_duration(gst.FORMAT_TIME)[0] / gst.SECOND
 
828
                #print "new_decoded_pad duration:", self.sound_file.duration
 
829
 
 
830
        def get_sound_file(self):
 
831
                return self.sound_file
 
832
 
 
833
        def get_input_uri(self):
 
834
                return self.sound_file.get_uri()
 
835
 
 
836
        def get_duration(self):
 
837
                """ return the total duration of the sound file """
 
838
                if not self.pipeline:
 
839
                        print "no pipeline"
 
840
                        return 0
 
841
                self.query_duration()
 
842
                return self.sound_file.duration
 
843
        
 
844
        def get_position(self):
 
845
                """ return the current pipeline position in the stream """
 
846
                return self.position
 
847
 
 
848
class TagReader(Decoder):
 
849
 
 
850
        """A GstPipeline background task for finding meta tags in a file."""
 
851
 
 
852
        def __init__(self, sound_file):
 
853
                Decoder.__init__(self, sound_file)
 
854
                self.found_tag_hook = None
 
855
                self.found_tags = False
 
856
                self.run_start_time = 0 
 
857
                self.add_command("fakesink")
 
858
 
 
859
        def set_found_tag_hook(self, found_tag_hook):
 
860
                self.found_tag_hook = found_tag_hook
 
861
 
 
862
 
 
863
        def found_tag(self, decoder, something, taglist):
 
864
 
 
865
                #debug("found_tags:", self.sound_file.get_filename_for_display())
 
866
                #debug("\ttitle=%s" % (taglist["title"]))
 
867
                #for k in taglist.keys():
 
868
                #       debug("\t%s=%s" % (k, taglist[k]))
 
869
                self.sound_file.add_tags(taglist)
 
870
 
 
871
                # tags from ogg vorbis files comes with two callbacks,
 
872
                # the first callback containing just the stream serial number.
 
873
                # The second callback contains the tags we're interested in.
 
874
                #if "serial" in taglist.keys():
 
875
                #       print "Error, serial tag in ogg/vorbis, tags will be lost"
 
876
                #       return
 
877
                
 
878
                # we want something useful in tags
 
879
                if "title" not in taglist.keys():
 
880
                        return
 
881
 
 
882
                self.found_tags = True
 
883
                self.sound_file.have_tags = True
 
884
 
 
885
                try:
 
886
                        self.sound_file.duration = self.pipeline.query_duration(gst.FORMAT_TIME)[0] / gst.SECOND
 
887
                except gst.QueryError:
 
888
                        pass
 
889
 
 
890
        def work(self):
 
891
                if not self.run_start_time:
 
892
                        self.run_start_time = time.time()
 
893
                        if self.sound_file.mime_type in tag_blacklist:
 
894
                                log("%s: type is %s, tag reading blacklisted" % (self.sound_file.get_filename_for_display(), self.sound_file.mime_type))
 
895
                                return False
 
896
 
 
897
                if time.time()-self.run_start_time > 5:
 
898
                        log("TagReader timeout:", self.sound_file.get_filename_for_display())
 
899
                        # stop looking for tags after 5s 
 
900
                        return False
 
901
                return Decoder.work(self) and not self.found_tags
 
902
 
 
903
        def finish(self):
 
904
                Decoder.finish(self)
 
905
                self.sound_file.tags_read = True
 
906
                if self.found_tag_hook:
 
907
                        gobject.idle_add(self.found_tag_hook, self)
 
908
 
 
909
 
 
910
class ConversionTargetExists(SoundConverterException):
 
911
 
 
912
        def __init__(self, uri):
 
913
                SoundConverterException.__init__(self, _("Target exists."),
 
914
                                                                                 (_("The output file %s already exists.")) % uri)
 
915
 
 
916
 
 
917
class Converter(Decoder):
 
918
 
 
919
        """A background task for converting files to another format."""
 
920
 
 
921
        def __init__(self, sound_file, output_filename, output_type):
 
922
                #print "Converter()"
 
923
                Decoder.__init__(self, sound_file)
 
924
 
 
925
                self.converting = True
 
926
                
 
927
                self.output_filename = output_filename
 
928
                self.output_type = output_type
 
929
                self.vorbis_quality = None
 
930
                self.mp3_bitrate = None
 
931
                self.mp3_mode = None
 
932
                self.mp3_quality = None
 
933
 
 
934
                self.overwrite = False
 
935
 
 
936
        #def setup(self):
 
937
        #       self.init()
 
938
        #       self.play()
 
939
 
 
940
        def init(self):
 
941
                #print "Converter.init()"
 
942
                self.encoders = {
 
943
                        "audio/x-vorbis": self.add_oggvorbis_encoder,
 
944
                        "audio/x-flac": self.add_flac_encoder,
 
945
                        "audio/x-wav": self.add_wav_encoder,
 
946
                        "audio/mpeg": self.add_mp3_encoder,
 
947
                }
 
948
 
 
949
                self.add_command("audioconvert")
 
950
                #TODO self.add_command("audioscale")
 
951
                
 
952
                encoder = self.encoders[self.output_type]()
 
953
                if not encoder:
 
954
                        # TODO: add proper error management when an encoder cannot be created
 
955
                        dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
 
956
                                                gtk.BUTTONS_OK, _("Cannot create a decoder for '%s' format.") % \
 
957
                                                self.output_type )
 
958
                        dialog.run()
 
959
                        dialog.hide()
 
960
                        return
 
961
                        
 
962
                self.add_command(encoder)
 
963
                
 
964
                uri = gnomevfs.URI(self.output_filename)
 
965
                dirname = uri.parent
 
966
                if dirname and not gnomevfs.exists(dirname):
 
967
                        log(_("Creating folder: '%s'") % dirname)
 
968
                        if not vfs_makedirs(str(dirname)):
 
969
                                # TODO add error management
 
970
                                dialog = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
 
971
                                                        gtk.BUTTONS_OK, _("Cannot create '%s' folder.") % \
 
972
                                                        dirname )
 
973
                                dialog.run()
 
974
                                dialog.hide()
 
975
                                return
 
976
        
 
977
                self.add_command('gnomevfssink location=%s' % uri)
 
978
                if self.overwrite and vfs_exists(self.output_filename):
 
979
                        log("overwriting '%s'" % self.output_filename)
 
980
                        vfs_unlink(self.output_filename)
 
981
                #log( _("Writing to: '%s'") % urllib.unquote(self.output_filename) )
 
982
 
 
983
        def finish(self):
 
984
                Pipeline.finish(self)
 
985
                
 
986
                # Copy file permissions
 
987
                try:
 
988
                        info = gnomevfs.get_file_info( self.sound_file.get_uri(),gnomevfs.FILE_INFO_FIELDS_PERMISSIONS)
 
989
                        gnomevfs.set_file_info(self.output_filename, info, gnomevfs.SET_FILE_INFO_PERMISSIONS)
 
990
                except:
 
991
                        log(_("Cannot set permission on '%s'") % gnomevfs.format_uri_for_display(self.output_filename))
 
992
 
 
993
        def get_position(self):
 
994
                return self.position
 
995
        
 
996
        def set_vorbis_quality(self, quality):
 
997
                self.vorbis_quality = quality
 
998
 
 
999
        def set_mp3_mode(self, mode):
 
1000
                self.mp3_mode = mode
 
1001
 
 
1002
        def set_mp3_quality(self, quality):
 
1003
                self.mp3_quality = quality
 
1004
 
 
1005
        def add_flac_encoder(self):
 
1006
                return "flacenc"
 
1007
 
 
1008
        def add_wav_encoder(self):
 
1009
                return "wavenc"
 
1010
 
 
1011
        def add_oggvorbis_encoder(self):
 
1012
                cmd = "vorbisenc"
 
1013
                if self.vorbis_quality is not None:
 
1014
                        cmd += " quality=%s" % self.vorbis_quality
 
1015
                cmd += " ! oggmux "
 
1016
                return cmd
 
1017
 
 
1018
        def add_mp3_encoder(self):
 
1019
        
 
1020
                cmd = "lame quality=2 "
 
1021
                
 
1022
                if self.mp3_mode is not None:
 
1023
                        properties = {
 
1024
                                "cbr" : (0,"bitrate"),
 
1025
                                "abr" : (3,"vbr-mean-bitrate"),
 
1026
                                "vbr" : (4,"vbr-quality")
 
1027
                        }
 
1028
 
 
1029
                        if properties[self.mp3_mode][0]:
 
1030
                                cmd += "xingheader=true "
 
1031
                        
 
1032
                        cmd += "vbr=%s " % properties[self.mp3_mode][0]
 
1033
                        if self.mp3_quality == 9:
 
1034
                                # GStreamer set max bitrate to 320 but lame uses
 
1035
                                # mpeg2 with vbr-quality==9, so max bitrate is 160
 
1036
                                cmd += "vbr-max-bitrate=160 "
 
1037
                        
 
1038
                        cmd += "%s=%s " % (properties[self.mp3_mode][1], self.mp3_quality)
 
1039
        
 
1040
                return cmd
 
1041
 
 
1042
class FileList:
 
1043
        """List of files added by the user."""
 
1044
 
 
1045
        # List of MIME types which we accept for drops.
 
1046
        drop_mime_types = ["text/uri-list", "text/plain", "STRING"]
 
1047
 
 
1048
        def __init__(self, window, glade):
 
1049
                self.window = window
 
1050
                self.tagreaders  = TaskQueue()
 
1051
                self.typefinders = TaskQueue()
 
1052
                # handle the current task for status
 
1053
 
 
1054
                self.filelist={}
 
1055
                
 
1056
                args = []
 
1057
                for name in ALL_COLUMNS:
 
1058
                        if name in VISIBLE_COLUMNS:
 
1059
                                args.append(gobject.TYPE_STRING)
 
1060
                        else:
 
1061
                                args.append(gobject.TYPE_PYOBJECT)
 
1062
                self.model = apply(gtk.ListStore, args)
 
1063
 
 
1064
                self.widget = glade.get_widget("filelist")
 
1065
                self.widget.set_model(self.model)
 
1066
                self.widget.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
 
1067
                
 
1068
                self.widget.drag_dest_set(gtk.DEST_DEFAULT_ALL, 
 
1069
                                                                        map(lambda i: 
 
1070
                                                                                (self.drop_mime_types[i], 0, i), 
 
1071
                                                                                range(len(self.drop_mime_types))),
 
1072
                                                                                gtk.gdk.ACTION_COPY)
 
1073
                self.widget.connect("drag_data_received", self.drag_data_received)
 
1074
 
 
1075
                renderer = gtk.CellRendererText()
 
1076
                for name in VISIBLE_COLUMNS:
 
1077
                        column = gtk.TreeViewColumn(name,
 
1078
                                                                                renderer, 
 
1079
                                                                                markup=ALL_COLUMNS.index(name))
 
1080
                        self.widget.append_column(column)
 
1081
        
 
1082
        def drag_data_received(self, widget, context, x, y, selection, 
 
1083
                                                   mime_id, time):
 
1084
 
 
1085
                if mime_id >= 0 and mime_id < len(self.drop_mime_types):
 
1086
                        file_list = []
 
1087
                        for uri in selection.data.split("\n"):
 
1088
                                uri = uri.strip()
 
1089
                                if uri:
 
1090
                                        info = gnomevfs.get_file_info(uri, gnomevfs.FILE_INFO_DEFAULT)
 
1091
                                        if info.type == gnomevfs.FILE_TYPE_DIRECTORY:
 
1092
                                                file_list.extend(vfs_walk(gnomevfs.URI(uri)))
 
1093
                                        else:
 
1094
                                                file_list.append(uri)
 
1095
                        context.finish(True, False, time)
 
1096
                        base = os.path.commonprefix(file_list)
 
1097
                        #[self.add_file(SoundFile(base, uri[len(base):])) for uri in file_list]
 
1098
                        #[self.add_file(SoundFile(uri, base)) for uri in file_list]
 
1099
                        self.add_uris(file_list)
 
1100
 
 
1101
        def get_files(self):
 
1102
                files = []
 
1103
                i = self.model.get_iter_first()
 
1104
                while i:
 
1105
                        f = {}
 
1106
                        for c in ALL_COLUMNS:
 
1107
                                f[c] = self.model.get_value(i, ALL_COLUMNS.index(c))
 
1108
                        files.append(f["META"])
 
1109
 
 
1110
                        i = self.model.iter_next(i)
 
1111
                return files
 
1112
        
 
1113
        def found_type(self, sound_file, mime):
 
1114
                #debug("found_type", sound_file.get_filename())
 
1115
 
 
1116
                self.append_file(sound_file)
 
1117
                self.window.set_sensitive()
 
1118
 
 
1119
                tagreader = TagReader(sound_file)
 
1120
                tagreader.set_found_tag_hook(self.append_file_tags)
 
1121
 
 
1122
                self.tagreaders.add(tagreader)
 
1123
                if not self.tagreaders.is_running():
 
1124
                        self.tagreaders.run()
 
1125
        
 
1126
        def add_uris(self, uris, base=None):
 
1127
 
 
1128
                files = []
 
1129
 
 
1130
                for uri in uris:
 
1131
                        try:
 
1132
                                info = gnomevfs.get_file_info(gnomevfs.URI(uri))
 
1133
                        except gnomevfs.NotFoundError:
 
1134
                                continue
 
1135
                        except gnomevfs.InvalidURIError:
 
1136
                                continue
 
1137
 
 
1138
                        if info.type == gnomevfs.FILE_TYPE_DIRECTORY:
 
1139
                                filelist = vfs_walk(gnomevfs.URI(uri))
 
1140
                                
 
1141
                                for f in filelist:
 
1142
                                        #f = f[len(base)+1:]
 
1143
                                        files.append(f)
 
1144
                        else:
 
1145
                                files.append(uri)
 
1146
                                
 
1147
                base,notused = os.path.split(os.path.commonprefix(files))
 
1148
                base += "/"
 
1149
 
 
1150
                for f in files:
 
1151
                        sound_file = SoundFile(f, base)
 
1152
                        if sound_file.get_uri() in self.filelist:
 
1153
                                log(_("file already present: '%s'") % sound_file.get_uri())
 
1154
                                continue 
 
1155
                        #print "adding: '%s'" % sound_file.get_filename_for_display()
 
1156
                        self.filelist[sound_file.get_uri()] = True
 
1157
 
 
1158
                        typefinder = TypeFinder(sound_file)
 
1159
                        typefinder.set_found_type_hook(self.found_type)
 
1160
                        self.typefinders.add(typefinder)
 
1161
 
 
1162
                if not self.typefinders.is_running():
 
1163
                        self.typefinders.queue_ended = self.typefinder_queue_ended
 
1164
                        self.typefinders.run()
 
1165
 
 
1166
        def typefinder_queue_ended(self):
 
1167
                print "typefinder_queue_ended"
 
1168
 
 
1169
        def format_cell(self, sound_file):
 
1170
                
 
1171
                template_tags    = "%(artist)s - <i>%(album)s</i> - <b>%(title)s</b>\n<small>%(filename)s</small>"
 
1172
                template_loading = "<i>%s</i>\n<small>%%(filename)s</small>" \
 
1173
                                                        % _("loading tags...")
 
1174
                template_notags  = '<span foreground="red">%s</span>\n<small>%%(filename)s</small>' \
 
1175
                                                        % _("no tags")
 
1176
 
 
1177
                params = {}
 
1178
                params["filename"] = markup_escape(urllib.unquote(sound_file.get_filename_for_display()))
 
1179
                for item in ("title", "artist", "album"):
 
1180
                        params[item] = markup_escape(sound_file.get_tag(item))
 
1181
                if sound_file["bitrate"]:
 
1182
                        params["bitrate"] = ", %s kbps" % (sound_file["bitrate"] / 1000)
 
1183
                else:
 
1184
                        params["bitrate"] = ""
 
1185
 
 
1186
                try:    
 
1187
                        if sound_file.have_tags:
 
1188
                                template = template_tags
 
1189
                        else:
 
1190
                                if sound_file.tags_read:
 
1191
                                        template = template_notags
 
1192
                                else:
 
1193
                                        template = template_loading
 
1194
 
 
1195
                        s = template % params
 
1196
                except UnicodeDecodeError:
 
1197
                        str = ""
 
1198
                        for c in markup_escape(urllib.unquote(sound_file.get_uri())):
 
1199
                                if ord(c) < 127:
 
1200
                                        str += c
 
1201
                                else:
 
1202
                                        str += '<span foreground="yellow" background="red"><b>!</b></span>'
 
1203
 
 
1204
                        error.show(_("Invalid character in filename!"), str)
 
1205
                        sys.exit(1)
 
1206
                                
 
1207
                return s
 
1208
 
 
1209
        def append_file(self, sound_file):
 
1210
 
 
1211
                #print "+", sound_file.get_filename_for_display()
 
1212
                iter = self.model.append()
 
1213
                sound_file.model = iter
 
1214
                self.model.set(iter, 0, self.format_cell(sound_file))
 
1215
                self.model.set(iter, 1, sound_file)
 
1216
                self.window.progressbar.pulse()
 
1217
                        
 
1218
        
 
1219
        def append_file_tags(self, tagreader):
 
1220
                sound_file = tagreader.get_sound_file()
 
1221
 
 
1222
                fields = {}
 
1223
                for key in ALL_COLUMNS:
 
1224
                        fields[key] = _("unknown")
 
1225
                fields["META"] = sound_file
 
1226
                fields["filename"] = sound_file.get_filename_for_display()
 
1227
 
 
1228
                self.model.set(sound_file.model, 0, self.format_cell(sound_file))
 
1229
                self.window.set_sensitive()
 
1230
                self.window.progressbar.pulse()
 
1231
 
 
1232
        def remove(self, iter):
 
1233
                uri = self.model.get(iter, 1)[0].get_uri()
 
1234
                del self.filelist[uri]
 
1235
                self.model.remove(iter)
 
1236
                
 
1237
        def is_nonempty(self):
 
1238
                try:
 
1239
                        self.model.get_iter((0,))
 
1240
                except ValueError:
 
1241
                        return False
 
1242
                return True
 
1243
 
 
1244
 
 
1245
class PreferencesDialog:
 
1246
 
 
1247
        root = "/apps/SoundConverter"
 
1248
        
 
1249
        basename_patterns = [
 
1250
                ("%(.inputname)s", _("Same as input, but with new suffix")),
 
1251
                ("%(track-number)02d-%(title)s", _("Track number - title")),
 
1252
                ("%(title)s", _("Track title")),
 
1253
                ("%(artist)s-%(title)s", _("Artist - title")),
 
1254
                ("Custom", _("Custom filename pattern")),
 
1255
        ]
 
1256
        
 
1257
        subfolder_patterns = [
 
1258
                ("%(artist)s/%(album)s", _("artist/album")),
 
1259
                ("%(artist)s-%(album)s", _("artist-album")),
 
1260
        ]
 
1261
        
 
1262
        defaults = {
 
1263
                "same-folder-as-input": 1,
 
1264
                "selected-folder": os.path.expanduser("~"),
 
1265
                "create-subfolders": 0,
 
1266
                "subfolder-pattern-index": 0,
 
1267
                "name-pattern-index": 0,
 
1268
                "custom-filename-pattern": "{Track} - {Title}",
 
1269
                "replace-messy-chars": 0,
 
1270
                "output-mime-type": "audio/x-vorbis",
 
1271
                "output-suffix": ".ogg",
 
1272
                "vorbis-quality": 0.6,
 
1273
                "mp3-mode": "vbr",                      # 0: cbr, 1: abr, 2: vbr
 
1274
                "mp3-cbr-quality": 192,
 
1275
                "mp3-abr-quality": 192,
 
1276
                "mp3-vbr-quality": 3,
 
1277
        }
 
1278
 
 
1279
        sensitive_names = ["vorbis_quality", "choose_folder", "create_subfolders",
 
1280
                                           "subfolder_pattern"]
 
1281
 
 
1282
        def __init__(self, glade):
 
1283
                self.gconf = gconf.client_get_default()
 
1284
                self.gconf.add_dir(self.root, gconf.CLIENT_PRELOAD_ONELEVEL)
 
1285
                self.dialog = glade.get_widget("prefsdialog")
 
1286
                self.into_selected_folder = glade.get_widget("into_selected_folder")
 
1287
                self.target_folder_chooser = glade.get_widget("target_folder_chooser")
 
1288
                self.basename_pattern = glade.get_widget("basename_pattern")
 
1289
                self.custom_filename_box = glade.get_widget("custom_filename_box")
 
1290
                self.custom_filename = glade.get_widget("custom_filename")
 
1291
                self.example = glade.get_widget("example_filename")
 
1292
                self.aprox_bitrate = glade.get_widget("aprox_bitrate")
 
1293
                self.quality_tabs = glade.get_widget("quality_tabs")
 
1294
 
 
1295
                self.target_bitrate = None
 
1296
                self.convert_setting_from_old_version()
 
1297
                
 
1298
                self.sensitive_widgets = {}
 
1299
                for name in self.sensitive_names:
 
1300
                        self.sensitive_widgets[name] = glade.get_widget(name)
 
1301
                        assert self.sensitive_widgets[name] != None
 
1302
                self.set_widget_initial_values(glade)
 
1303
                self.set_sensitive()
 
1304
 
 
1305
 
 
1306
        def convert_setting_from_old_version(self):
 
1307
                """ try to convert previous settings"""
 
1308
                
 
1309
                # TODO: why not just reseting the settings if we cannot load them ?
 
1310
                        
 
1311
                # vorbis quality was stored as an int enum
 
1312
                try:
 
1313
                        self.get_float("vorbis-quality")
 
1314
                except gobject.GError:
 
1315
                        log("converting old vorbis setting...")
 
1316
                        old_quality = self.get_int("vorbis-quality")
 
1317
                        self.gconf.unset(self.path("vorbis-quality"))
 
1318
                        quality_setting = (0,0.2,0.3,0.6,0.8)
 
1319
                        self.set_float("vorbis-quality", quality_setting[old_quality])
 
1320
                        
 
1321
                # mp3 quality was stored as an int enum
 
1322
                cbr = self.get_int("mp3-cbr-quality")
 
1323
                if cbr <= 4:
 
1324
                        log("converting old mp3 quality setting... (%d)" % cbr)
 
1325
 
 
1326
                        abr = self.get_int("mp3-abr-quality")
 
1327
                        vbr = self.get_int("mp3-vbr-quality")
 
1328
 
 
1329
                        cbr_quality = (64, 96, 128, 192, 256)
 
1330
                        vbr_quality = (9, 7, 5, 3, 1)
 
1331
 
 
1332
                        self.set_int("mp3-cbr-quality", cbr_quality[cbr])
 
1333
                        self.set_int("mp3-abr-quality", cbr_quality[abr])
 
1334
                        self.set_int("mp3-vbr-quality", vbr_quality[vbr])
 
1335
 
 
1336
                # mp3 mode was stored as an int enum
 
1337
                try:
 
1338
                        self.get_string("mp3-mode")
 
1339
                except gobject.GError:
 
1340
                        log("converting old mp3 mode setting...")
 
1341
                        old_mode = self.get_int("mp3-mode")
 
1342
                        self.gconf.unset(self.path("mp3-mode"))
 
1343
                        modes = ("cbr","abr","vbr")
 
1344
                        self.set_string("mp3-mode", modes[old_mode])
 
1345
 
 
1346
                self.gconf.clear_cache()
 
1347
 
 
1348
        def set_widget_initial_values(self, glade):
 
1349
                
 
1350
                self.quality_tabs.set_show_tabs(False)
 
1351
                
 
1352
                if self.get_int("same-folder-as-input"):
 
1353
                        w = glade.get_widget("same_folder_as_input")
 
1354
                else:
 
1355
                        w = glade.get_widget("into_selected_folder")
 
1356
                w.set_active(True)
 
1357
                
 
1358
                self.target_folder_chooser.set_filename(
 
1359
                        self.get_string("selected-folder"))
 
1360
                self.update_selected_folder()
 
1361
        
 
1362
                w = glade.get_widget("create_subfolders")
 
1363
                w.set_active(self.get_int("create-subfolders"))
 
1364
                
 
1365
                w = glade.get_widget("subfolder_pattern")
 
1366
                model = w.get_model()
 
1367
                model.clear()
 
1368
                for pattern, desc in self.subfolder_patterns:
 
1369
                        i = model.append()
 
1370
                        model.set(i, 0, desc)
 
1371
                w.set_active(self.get_int("subfolder-pattern-index"))
 
1372
 
 
1373
                if self.get_int("replace-messy-chars"):
 
1374
                        w = glade.get_widget("replace_messy_chars")
 
1375
                        w.set_active(True)
 
1376
 
 
1377
                mime_type = self.get_string("output-mime-type")
 
1378
 
 
1379
                # desactivate mp3 output if encoder plugin is not present
 
1380
                if not gst.element_factory_find("lame"):
 
1381
                        log("LAME GStreamer plugin not found, desactivating MP3 output.")
 
1382
                        w = glade.get_widget("output_mime_type_mp3")
 
1383
                        w.set_sensitive(False)
 
1384
                        mime_type = self.defaults["output-mime-type"]
 
1385
                        
 
1386
                
 
1387
                widget_name = {
 
1388
                                                "audio/x-vorbis": "output_mime_type_ogg_vorbis",
 
1389
                                                "audio/x-flac": "output_mime_type_flac",
 
1390
                                                "audio/x-wav": "output_mime_type_wav",
 
1391
                                                "audio/mpeg": "output_mime_type_mp3",
 
1392
                                        }.get(mime_type, None)
 
1393
                if widget_name:
 
1394
                        w = glade.get_widget(widget_name)
 
1395
                        w.set_active(True)
 
1396
                        self.change_mime_type(mime_type)
 
1397
                        
 
1398
                w = glade.get_widget("vorbis_quality")
 
1399
                quality = self.get_float("vorbis-quality")
 
1400
                quality_setting = {0:0 ,0.2:1 ,0.4:2 ,0.6:3 , 0.8:4}
 
1401
                for k, v in quality_setting.iteritems():
 
1402
                        if abs(quality-k) < 0.01:
 
1403
                                w.set_active(v)
 
1404
                        
 
1405
                self.mp3_quality = glade.get_widget("mp3_quality")
 
1406
                self.mp3_mode = glade.get_widget("mp3_mode")
 
1407
 
 
1408
                mode = self.get_string("mp3-mode")
 
1409
                self.change_mp3_mode(mode)
 
1410
 
 
1411
                w = glade.get_widget("basename_pattern")
 
1412
                model = w.get_model()
 
1413
                model.clear()
 
1414
                for pattern, desc in self.basename_patterns:
 
1415
                        iter = model.append()
 
1416
                        model.set(iter, 0, desc)
 
1417
                w.set_active(self.get_int("name-pattern-index"))
 
1418
 
 
1419
                
 
1420
                self.custom_filename.set_text(self.get_string("custom-filename-pattern"))
 
1421
                if self.basename_pattern.get_active() == len(self.basename_patterns)-1:
 
1422
                        self.custom_filename_box.set_sensitive(True)
 
1423
                else:
 
1424
                        self.custom_filename_box.set_sensitive(False)
 
1425
                        
 
1426
                self.update_example()
 
1427
 
 
1428
        def update_selected_folder(self):
 
1429
                self.into_selected_folder.set_label(_("Into folder %s") % 
 
1430
                                                                                self.get_string("selected-folder"))
 
1431
 
 
1432
 
 
1433
        def get_bitrate_from_settings(self):
 
1434
                bitrate = 0
 
1435
                aprox = True
 
1436
                mode = self.get_string("mp3-mode")
 
1437
 
 
1438
                mime_type = self.get_string("output-mime-type")
 
1439
                
 
1440
                if mime_type == "audio/x-vorbis":
 
1441
                        quality = self.get_float("vorbis-quality")*10
 
1442
                        quality = int(quality)
 
1443
                        bitrates = (64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 500)
 
1444
                        bitrate = bitrates[quality]
 
1445
                        
 
1446
                elif mime_type == "audio/mpeg":
 
1447
                        quality = {
 
1448
                                "cbr": "mp3-cbr-quality",
 
1449
                                "abr": "mp3-abr-quality",
 
1450
                                "vbr": "mp3-vbr-quality"
 
1451
                        }
 
1452
                        bitrate = self.get_int(quality[mode])
 
1453
                        if mode == "vbr":
 
1454
                                # hum, not really, but who cares? :)
 
1455
                                bitrates = (320, 256, 224, 192, 160, 128, 112, 96, 80, 64)
 
1456
                                bitrate = bitrates[bitrate]
 
1457
                        if mode == "cbr":
 
1458
                                aprox = False
 
1459
 
 
1460
                if bitrate:
 
1461
                        if aprox:
 
1462
                                return "~%d kbps" % bitrate
 
1463
                        else:
 
1464
                                return "%d kbps" % bitrate
 
1465
                else:
 
1466
                        return "N/A"
 
1467
 
 
1468
 
 
1469
        def update_example(self):
 
1470
                sound_file = SoundFile(os.path.expanduser("~/foo/bar.flac"))
 
1471
                sound_file.add_tags({
 
1472
                        "artist": "<b>{Artist}</b>", 
 
1473
                        "title": "<b>{Title}</b>", 
 
1474
                        "album": "<b>{Album}</b>",
 
1475
                        "track-number": 1L,
 
1476
                        "track-count": 11L,
 
1477
                })
 
1478
                self.example.set_markup(self.generate_filename(sound_file, for_display=True))
 
1479
                
 
1480
                markup = _("<small>Target bitrate: %s</small>") % self.get_bitrate_from_settings()
 
1481
                self.aprox_bitrate.set_markup( markup )
 
1482
 
 
1483
        def generate_filename(self, sound_file, for_display=False):
 
1484
                self.gconf.clear_cache()
 
1485
                output_type = self.get_string("output-mime-type")
 
1486
                output_suffix = {
 
1487
                                                "audio/x-vorbis": ".ogg",
 
1488
                                                "audio/x-flac": ".flac",
 
1489
                                                "audio/x-wav": ".wav",
 
1490
                                                "audio/mpeg": ".mp3",
 
1491
                                        }.get(output_type, None)
 
1492
 
 
1493
                generator = TargetNameGenerator()
 
1494
                generator.set_target_suffix(output_suffix)
 
1495
                if self.get_int("same-folder-as-input"):
 
1496
                        tuple = urlparse.urlparse(sound_file.get_uri())
 
1497
                        path = tuple[2]
 
1498
                        generator.set_folder(os.path.dirname(path))
 
1499
                else:
 
1500
                        path, filename = os.path.split(sound_file.get_filename())
 
1501
                        path = ""
 
1502
                
 
1503
                        generator.set_folder(os.path.join(self.get_string("selected-folder"), path))
 
1504
                        if self.get_int("create-subfolders"):
 
1505
                                generator.set_subfolder_pattern(
 
1506
                                        self.get_subfolder_pattern())
 
1507
                generator.set_basename_pattern(self.get_basename_pattern())
 
1508
                if for_display:
 
1509
                        generator.set_replace_messy_chars(False)
 
1510
                        return urllib.unquote(generator.get_target_name(sound_file))
 
1511
                else:
 
1512
                        generator.set_replace_messy_chars(
 
1513
                                self.get_int("replace-messy-chars"))
 
1514
                        return generator.get_target_name(sound_file)
 
1515
        
 
1516
        def process_custom_pattern(self, pattern):
 
1517
                        pattern = pattern.replace("{Artist}", "%(artist)s")
 
1518
                        pattern = pattern.replace("{Album}", "%(album)s")
 
1519
                        pattern = pattern.replace("{Title}", "%(title)s")
 
1520
                        pattern = pattern.replace("{Track}", "%(track-number)02d")
 
1521
                        pattern = pattern.replace("{Total}", "%(track-total)02d")
 
1522
                        return pattern
 
1523
 
 
1524
        def set_sensitive(self):
 
1525
        
 
1526
                #TODO
 
1527
                return
 
1528
        
 
1529
                for widget in self.sensitive_widgets.values():
 
1530
                        widget.set_sensitive(False)
 
1531
                
 
1532
                x = self.get_int("same-folder-as-input")
 
1533
                for name in ["choose_folder", "create_subfolders", 
 
1534
                                         "subfolder_pattern"]:
 
1535
                        self.sensitive_widgets[name].set_sensitive(not x)
 
1536
                
 
1537
                self.sensitive_widgets["vorbis_quality"].set_sensitive(
 
1538
                        self.get_string("output-mime-type") == "audio/x-vorbis")
 
1539
 
 
1540
        def path(self, key):
 
1541
                assert self.defaults.has_key(key)
 
1542
                return "%s/%s" % (self.root, key)
 
1543
 
 
1544
        def get_with_default(self, getter, key):
 
1545
                if self.gconf.get(self.path(key)) is None:
 
1546
                        return self.defaults[key]
 
1547
                else:
 
1548
                        return getter(self.path(key))
 
1549
 
 
1550
        def get_int(self, key):
 
1551
                return self.get_with_default(self.gconf.get_int, key)
 
1552
 
 
1553
        def set_int(self, key, value):
 
1554
                self.gconf.set_int(self.path(key), value)
 
1555
 
 
1556
        def get_float(self, key):
 
1557
                return self.get_with_default(self.gconf.get_float, key)
 
1558
 
 
1559
        def set_float(self, key, value):
 
1560
                self.gconf.set_float(self.path(key), value)
 
1561
 
 
1562
        def get_string(self, key):
 
1563
                return self.get_with_default(self.gconf.get_string, key)
 
1564
 
 
1565
        def set_string(self, key, value):
 
1566
                self.gconf.set_string(self.path(key), value)
 
1567
 
 
1568
        def run(self):
 
1569
                self.dialog.run()
 
1570
                self.dialog.hide()
 
1571
 
 
1572
        def on_same_folder_as_input_toggled(self, button):
 
1573
                if button.get_active():
 
1574
                        self.set_int("same-folder-as-input", 1)
 
1575
                        self.set_sensitive()
 
1576
                        self.update_example()
 
1577
                        
 
1578
        def on_into_selected_folder_toggled(self, button):
 
1579
                if button.get_active():
 
1580
                        self.set_int("same-folder-as-input", 0)
 
1581
                        self.set_sensitive()
 
1582
                        self.update_example()
 
1583
 
 
1584
        def on_choose_folder_clicked(self, button):
 
1585
                ret = self.target_folder_chooser.run()
 
1586
                self.target_folder_chooser.hide()
 
1587
                if ret == gtk.RESPONSE_OK:
 
1588
                        folder = self.target_folder_chooser.get_filename()
 
1589
                        if folder:
 
1590
                                self.set_string("selected-folder", folder)
 
1591
                                self.update_selected_folder()
 
1592
                                self.update_example()
 
1593
 
 
1594
        def on_create_subfolders_toggled(self, button):
 
1595
                if button.get_active():
 
1596
                        self.set_int("create-subfolders", 1)
 
1597
                else:
 
1598
                        self.set_int("create-subfolders", 0)
 
1599
                self.update_example()
 
1600
 
 
1601
        def on_subfolder_pattern_changed(self, combobox):
 
1602
                self.set_int("subfolder-pattern-index", combobox.get_active())
 
1603
                self.update_example()
 
1604
                
 
1605
        def get_subfolder_pattern(self):
 
1606
                index = self.get_int("subfolder-pattern-index")
 
1607
                if index < 0 or index >= len(self.subfolder_patterns):
 
1608
                        index = 0
 
1609
                return self.subfolder_patterns[index][0]
 
1610
 
 
1611
        def on_basename_pattern_changed(self, combobox):
 
1612
                self.set_int("name-pattern-index", combobox.get_active())
 
1613
                if combobox.get_active() == len(self.basename_patterns)-1:
 
1614
                        self.custom_filename_box.set_sensitive(True)
 
1615
                else:
 
1616
                        self.custom_filename_box.set_sensitive(False)
 
1617
                self.update_example()
 
1618
 
 
1619
        def get_basename_pattern(self):
 
1620
                index = self.get_int("name-pattern-index")
 
1621
                if index < 0 or index >= len(self.basename_patterns):
 
1622
                        index = 0
 
1623
                if self.basename_pattern.get_active() == len(self.basename_patterns)-1:
 
1624
                        return self.process_custom_pattern(self.custom_filename.get_text())
 
1625
                else:
 
1626
                        return self.basename_patterns[index][0]
 
1627
        
 
1628
        def on_custom_filename_changed(self, entry):
 
1629
                self.set_string("custom-filename-pattern", entry.get_text())
 
1630
                self.update_example()
 
1631
        
 
1632
        def on_replace_messy_chars_toggled(self, button):
 
1633
                if button.get_active():
 
1634
                        self.set_int("replace-messy-chars", 1)
 
1635
                else:
 
1636
                        self.set_int("replace-messy-chars", 0)
 
1637
                self.update_example()
 
1638
 
 
1639
        def change_mime_type(self, mime_type):
 
1640
                self.set_string("output-mime-type", mime_type)
 
1641
                self.set_sensitive()
 
1642
                self.update_example()
 
1643
                tabs = {
 
1644
                                                "audio/x-vorbis": 0,
 
1645
                                                "audio/mpeg": 1,
 
1646
                                                "audio/x-flac": 2,
 
1647
                                                "audio/x-wav": 3,
 
1648
                }
 
1649
                self.quality_tabs.set_current_page(tabs[mime_type])
 
1650
 
 
1651
        def on_output_mime_type_ogg_vorbis_toggled(self, button):
 
1652
                if button.get_active():
 
1653
                        self.change_mime_type("audio/x-vorbis")
 
1654
 
 
1655
        def on_output_mime_type_flac_toggled(self, button):
 
1656
                if button.get_active():
 
1657
                        self.change_mime_type("audio/x-flac")
 
1658
                
 
1659
        def on_output_mime_type_wav_toggled(self, button):
 
1660
                if button.get_active():
 
1661
                        self.change_mime_type("audio/x-wav")
 
1662
 
 
1663
        def on_output_mime_type_mp3_toggled(self, button):
 
1664
                if button.get_active():
 
1665
                        self.change_mime_type("audio/mpeg")
 
1666
 
 
1667
        def on_vorbis_quality_changed(self, combobox):
 
1668
                quality = (0,0.2,0.4,0.6,0.8)
 
1669
                self.set_float("vorbis-quality", quality[combobox.get_active()])
 
1670
                
 
1671
                self.update_example()
 
1672
 
 
1673
        def change_mp3_mode(self, mode):
 
1674
        
 
1675
                keys = { "cbr": 0, "abr": 1, "vbr": 2 }
 
1676
                self.mp3_mode.set_active(keys[mode]);
 
1677
        
 
1678
                keys = { 
 
1679
                        "cbr": "mp3-cbr-quality",
 
1680
                        "abr": "mp3-abr-quality",
 
1681
                        "vbr": "mp3-vbr-quality",
 
1682
                }
 
1683
                quality = self.get_int(keys[mode])
 
1684
                
 
1685
                quality_to_preset = {
 
1686
                        "cbr": {64:0, 96:1, 128:2, 192:3, 256:4},
 
1687
                        "abr": {64:0, 96:1, 128:2, 192:3, 256:4},
 
1688
                        "vbr": {9:0,   7:1,       5:2,   3:3,   1:4}, # inverted !
 
1689
                }
 
1690
                        
 
1691
                if quality in quality_to_preset[mode]:
 
1692
                        self.mp3_quality.set_active(quality_to_preset[mode][quality])
 
1693
                
 
1694
                self.update_example()
 
1695
 
 
1696
        def on_mp3_mode_changed(self, combobox):
 
1697
                mode = ("cbr","abr","vbr")[combobox.get_active()]
 
1698
                self.set_string("mp3-mode", mode)
 
1699
                self.change_mp3_mode(mode)
 
1700
 
 
1701
        def on_mp3_quality_changed(self, combobox):
 
1702
                keys = {
 
1703
                        "cbr": "mp3-cbr-quality",
 
1704
                        "abr": "mp3-abr-quality",
 
1705
                        "vbr": "mp3-vbr-quality"
 
1706
                }
 
1707
                quality = {
 
1708
                        "cbr": (64, 96, 128, 192, 256),
 
1709
                        "abr": (64, 96, 128, 192, 256),
 
1710
                        "vbr": (9, 7, 5, 3, 1),
 
1711
                }
 
1712
                mode = self.get_string("mp3-mode")
 
1713
                self.set_int(keys[mode], quality[mode][combobox.get_active()])
 
1714
 
 
1715
                self.update_example()
 
1716
 
 
1717
 
 
1718
class ConverterQueueCanceled(SoundConverterException):
 
1719
 
 
1720
        """Exception thrown when a ConverterQueue is canceled."""
 
1721
 
 
1722
        def __init__(self):
 
1723
                SoundConverterException.__init__(self, _("Convertion Canceled"), "")
 
1724
 
 
1725
 
 
1726
class ConverterQueue(TaskQueue):
 
1727
 
 
1728
        """Background task for converting many files."""
 
1729
 
 
1730
        def __init__(self, window):
 
1731
                TaskQueue.__init__(self)
 
1732
                self.window = window
 
1733
                self.overwrite_action = None
 
1734
                self.reset_counters()
 
1735
                
 
1736
        def reset_counters(self):
 
1737
                self.total_duration = 0
 
1738
                self.duration_processed = 0
 
1739
                self.overwrite_action = None
 
1740
 
 
1741
        def add(self, sound_file):
 
1742
        
 
1743
                output_filename = self.window.prefs.generate_filename(sound_file)
 
1744
                path = urlparse.urlparse(output_filename) [2]
 
1745
                path = urllib.unquote(path)
 
1746
        
 
1747
                exists = True
 
1748
                try:
 
1749
                        gnomevfs.get_file_info(gnomevfs.URI((output_filename)))
 
1750
                except gnomevfs.NotFoundError:
 
1751
                        exists = False
 
1752
                except gnomevfs.InvalidURIError:
 
1753
                        log("Invalid URI: '%s'" % output_filename)
 
1754
                        return
 
1755
                                
 
1756
                if exists:
 
1757
                        if self.overwrite_action != None:
 
1758
                                result = self.overwrite_action
 
1759
                        else:
 
1760
                                dialog = self.window.existsdialog
 
1761
 
 
1762
                                dpath = os.path.basename(path)
 
1763
                                dpath = markup_escape(dpath)
 
1764
 
 
1765
                                msg = \
 
1766
                                _("The output file <i>%s</i>\n exists already.\n Do you want to skip the file, overwrite it or cancel the conversion?\n") % \
 
1767
                                ( dpath )
 
1768
 
 
1769
                                dialog.message.set_markup(msg)
 
1770
 
 
1771
                                if self.overwrite_action != None:
 
1772
                                        dialog.apply_to_all.set_active(True)
 
1773
                                else:
 
1774
                                        dialog.apply_to_all.set_active(False)
 
1775
 
 
1776
                                result = dialog.run()
 
1777
                                dialog.hide()
 
1778
 
 
1779
                                if dialog.apply_to_all.get_active():
 
1780
                                        if result == 1 or result == 0:
 
1781
                                                self.overwrite_action = result
 
1782
 
 
1783
 
 
1784
                        if result == 1: 
 
1785
                                # overwrite
 
1786
                                try:
 
1787
                                        vfs_unlink(output_filename)
 
1788
                                except gnomevfs.NotFoundError:
 
1789
                                        pass
 
1790
                        elif result == 0: 
 
1791
                                # skip file
 
1792
                                return
 
1793
                        else:
 
1794
                                # cancel operation
 
1795
                                # TODO
 
1796
                                raise ConverterQueueCanceled()
 
1797
                        
 
1798
                c = Converter(sound_file, output_filename, 
 
1799
                                          self.window.prefs.get_string("output-mime-type"))
 
1800
                c.set_vorbis_quality(self.window.prefs.get_float("vorbis-quality"))
 
1801
                
 
1802
                quality = {
 
1803
                        "cbr": "mp3-cbr-quality",
 
1804
                        "abr": "mp3-abr-quality",
 
1805
                        "vbr": "mp3-vbr-quality"
 
1806
                }
 
1807
                mode = self.window.prefs.get_string("mp3-mode")
 
1808
                c.set_mp3_mode(mode)
 
1809
                c.set_mp3_quality(self.window.prefs.get_int(quality[mode]))
 
1810
                c.init()
 
1811
                TaskQueue.add(self, c)
 
1812
                self.total_duration += c.get_duration()
 
1813
 
 
1814
        def work_hook(self, task):
 
1815
                gobject.idle_add(self.set_progress, (task))
 
1816
 
 
1817
        def get_progress(self, task):
 
1818
                return (self.duration_processed + task.get_position()) / self.total_duration
 
1819
 
 
1820
        def set_progress(self, task):
 
1821
                t = self.get_current_task()
 
1822
                f = ""
 
1823
                if t:
 
1824
                        f = t.sound_file.get_filename_for_display()
 
1825
 
 
1826
                self.window.set_progress(self.duration_processed + task.get_position(),
 
1827
                                                         self.total_duration, f)
 
1828
                return False
 
1829
 
 
1830
        def finish_hook(self, task):
 
1831
                #print "finished: %d+=%d" % (self.duration_processed, task.get_duration())
 
1832
                self.duration_processed += task.get_duration()
 
1833
 
 
1834
        def finish(self):
 
1835
                TaskQueue.finish(self)
 
1836
                self.reset_counters()
 
1837
                self.window.set_progress(0, 0)
 
1838
                self.window.set_sensitive()
 
1839
                self.window.conversion_ended()
 
1840
                total_time = self.run_finish_time - self.run_start_time
 
1841
                self.window.set_status(_("Conversion done, in %s") % 
 
1842
                                                           self.format_time(total_time))
 
1843
 
 
1844
        def format_time(self, seconds):
 
1845
                units = [(86400, "d"),
 
1846
                                 (3600, "h"),
 
1847
                                 (60, "m"),
 
1848
                                 (1, "s")]
 
1849
                seconds = round(seconds)
 
1850
                result = []
 
1851
                for factor, name in units:
 
1852
                        count = int(seconds / factor)
 
1853
                        seconds -= count * factor
 
1854
                        if count > 0 or (factor == 1 and not result):
 
1855
                                result.append("%d %s" % (count, name))
 
1856
                assert seconds == 0
 
1857
                return " ".join(result)
 
1858
 
 
1859
        def stop(self):
 
1860
                TaskQueue.stop(self)
 
1861
                self.window.set_progress(0, 0)
 
1862
                self.window.set_sensitive()
 
1863
 
 
1864
class CustomFileChooser:
 
1865
        """
 
1866
        Custom file chooser.\n
 
1867
        """
 
1868
        def __init__(self):
 
1869
                """
 
1870
                Constructor
 
1871
                Load glade object, create a combobox
 
1872
                """
 
1873
                xml = gtk.glade.XML(GLADE,"custom_file_chooser")
 
1874
                self.dlg = xml.get_widget("custom_file_chooser")
 
1875
                self.dlg.set_title(_("Open a file"))
 
1876
                
 
1877
                # setup
 
1878
                self.fcw = xml.get_widget("filechooserwidget")
 
1879
                self.fcw.set_local_only(not use_gnomevfs)
 
1880
                self.fcw.set_select_multiple(True)
 
1881
                
 
1882
                self.pattern = []
 
1883
                
 
1884
                # Create combobox model
 
1885
                self.combo = xml.get_widget("filtercombo")
 
1886
                self.combo.connect("changed",self.on_combo_changed)
 
1887
                self.store = gtk.ListStore(str)
 
1888
                self.combo.set_model(self.store)
 
1889
                combo_rend = gtk.CellRendererText()
 
1890
                self.combo.pack_start(combo_rend, True)
 
1891
                self.combo.add_attribute(combo_rend, 'text', 0)
 
1892
        
 
1893
                # get all (gstreamer) knew files Todo
 
1894
                for files in filepattern:
 
1895
                        self.add_pattern(files[0],files[1])
 
1896
                self.combo.set_active(0)
 
1897
                
 
1898
        def add_pattern(self,name,pat):
 
1899
                """
 
1900
                Add a new pattern to the combobox.
 
1901
                @param name: The pattern name.
 
1902
                @type name: string
 
1903
                @param pat: the pattern
 
1904
                @type pat: string
 
1905
                """
 
1906
                self.pattern.append(pat)
 
1907
                self.store.append(["%s (%s)" %(name,pat)])
 
1908
                
 
1909
        def on_combo_changed(self,w):
 
1910
                """
 
1911
                Callback for combobox "changed" signal\n
 
1912
                Set a new filter for the filechooserwidget
 
1913
                """
 
1914
                filter = gtk.FileFilter()
 
1915
                filter.add_pattern(self.pattern[self.combo.get_active()])
 
1916
                self.fcw.set_filter(filter)
 
1917
                
 
1918
        def run(self):
 
1919
                """
 
1920
                Display the dialog
 
1921
                """
 
1922
                return self.dlg.run()
 
1923
                
 
1924
        def hide(self):
 
1925
                """
 
1926
                Hide the dialog
 
1927
                """
 
1928
                self.dlg.hide()
 
1929
                
 
1930
        def get_uris(self):
 
1931
                """
 
1932
                Return all the selected uris
 
1933
                """
 
1934
                return self.fcw.get_uris()
 
1935
                
 
1936
                
 
1937
class SoundConverterWindow:
 
1938
 
 
1939
        """Main application class."""
 
1940
 
 
1941
        sensitive_names = [ "remove", "convert_button" ]
 
1942
        unsensitive_when_converting = [ "remove", "prefs_button" ,"toolbutton_addfile", "toolbutton_addfolder", "filelist", "menubar" ]
 
1943
 
 
1944
        def __init__(self, glade):
 
1945
        
 
1946
                self.widget = glade.get_widget("window")
 
1947
                self.filelist = FileList(self, glade)
 
1948
                self.filelist_selection = self.filelist.widget.get_selection()
 
1949
                self.filelist_selection.connect("changed", self.selection_changed)
 
1950
                self.existsdialog = glade.get_widget("existsdialog")
 
1951
                self.existsdialog.message = glade.get_widget("exists_message")
 
1952
                self.existsdialog.apply_to_all = glade.get_widget("apply_to_all")
 
1953
                self.existslabel = glade.get_widget("existslabel")
 
1954
                self.progressbar = glade.get_widget("progressbar")
 
1955
                self.status = glade.get_widget("statustext")
 
1956
                self.about = glade.get_widget("about")
 
1957
                self.prefs = PreferencesDialog(glade)
 
1958
 
 
1959
                self.progressframe = glade.get_widget("progress_frame")
 
1960
                self.statusframe = glade.get_widget("status_frame")
 
1961
                self.progressfile = glade.get_widget("progressfile")
 
1962
 
 
1963
                self.addchooser = CustomFileChooser()
 
1964
                self.addfolderchooser = gtk.FileChooserDialog(_("Add Folder..."),
 
1965
                                                                                                self.widget,
 
1966
                                                                                                gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
 
1967
                                                                                                (gtk.STOCK_CANCEL, 
 
1968
                                                                                                        gtk.RESPONSE_CANCEL,
 
1969
                                                                                                        gtk.STOCK_OPEN,
 
1970
                                                                                                        gtk.RESPONSE_OK))
 
1971
                self.addfolderchooser.set_select_multiple(True)
 
1972
                self.addfolderchooser.set_local_only(not use_gnomevfs)
 
1973
 
 
1974
                self.connect(glade, [self.prefs])
 
1975
                
 
1976
                self.about.set_property("name", NAME)
 
1977
                self.about.set_property("version", VERSION)
 
1978
 
 
1979
                self.convertion_waiting = False
 
1980
 
 
1981
                self.converter = ConverterQueue(self)
 
1982
                
 
1983
                self._lock_convert_button = False
 
1984
                
 
1985
                self.sensitive_widgets = {}
 
1986
                for name in self.sensitive_names:
 
1987
                        self.sensitive_widgets[name] = glade.get_widget(name)
 
1988
                for name in self.unsensitive_when_converting:
 
1989
                        self.sensitive_widgets[name] = glade.get_widget(name)
 
1990
 
 
1991
                self.set_sensitive()
 
1992
 
 
1993
                self.set_status()
 
1994
 
 
1995
        # This bit of code constructs a list of methods for binding to Gtk+
 
1996
        # signals. This way, we don't have to maintain a list manually,
 
1997
        # saving editing effort. It's enough to add a method to the suitable
 
1998
        # class and give the same name in the .glade file.
 
1999
        
 
2000
        def connect(self, glade, objects):
 
2001
                dicts = {}
 
2002
                for o in [self] + objects:
 
2003
                        for name, member in inspect.getmembers(o):
 
2004
                                dicts[name] = member
 
2005
                glade.signal_autoconnect(dicts)
 
2006
 
 
2007
        def close(self, *args):
 
2008
                self.converter.stop()
 
2009
                self.widget.destroy()
 
2010
                gtk.main_quit()
 
2011
                return True 
 
2012
 
 
2013
        on_window_delete_event = close
 
2014
        on_quit_activate = close
 
2015
        on_quit_button_clicked = close
 
2016
 
 
2017
        def on_add_activate(self, *args):
 
2018
                ret = self.addchooser.run()
 
2019
                self.addchooser.hide()
 
2020
                if ret == gtk.RESPONSE_OK:
 
2021
                        files = []
 
2022
                        #for uri in self.addchooser.get_uris():
 
2023
                        #       files.append(SoundFile(uri))
 
2024
                        #self.filelist.add_files(files)
 
2025
                        self.filelist.add_uris(self.addchooser.get_uris())
 
2026
                self.set_sensitive()
 
2027
 
 
2028
 
 
2029
        def on_addfolder_activate(self, *args):
 
2030
                ret = self.addfolderchooser.run()
 
2031
                self.addfolderchooser.hide()
 
2032
                if ret == gtk.RESPONSE_OK:
 
2033
                        folders = self.addfolderchooser.get_uris()
 
2034
                        
 
2035
                        self.filelist.add_uris(folders)
 
2036
 
 
2037
                        #base,notused = os.path.split(os.path.commonprefix(folders))
 
2038
                        #filelist = []
 
2039
                        #files = []
 
2040
                        #for folder in folders:
 
2041
                        #       filelist.extend(vfs_walk(gnomevfs.URI(folder)))
 
2042
                        #for f in filelist:
 
2043
                        #       f = f[len(base)+1:]
 
2044
                        #       files.append(SoundFile(base+"/", f))
 
2045
                        #self.filelist.add_files(files)
 
2046
                self.set_sensitive()
 
2047
 
 
2048
        def on_remove_activate(self, *args):
 
2049
                model, paths = self.filelist_selection.get_selected_rows()
 
2050
                while paths:
 
2051
                        i = self.filelist.model.get_iter(paths[0])
 
2052
                        self.filelist.remove(i)
 
2053
                        model, paths = self.filelist_selection.get_selected_rows()
 
2054
                self.set_sensitive()
 
2055
 
 
2056
        def do_convert(self):
 
2057
                try:
 
2058
                        for sound_file in self.filelist.get_files():
 
2059
                                self.converter.add(sound_file)
 
2060
                except ConverterQueueCanceled:
 
2061
                        log(_("canceling conversion."))
 
2062
                else:
 
2063
                        self.set_status("")
 
2064
                        self.converter.run()
 
2065
                        self.convertion_waiting = False
 
2066
                        self.set_sensitive()
 
2067
                return False
 
2068
 
 
2069
        def wait_tags_and_convert(self):
 
2070
                not_ready = [s for s in self.filelist.get_files() if not s.tags_read]
 
2071
                if not_ready:
 
2072
                        self.progressbar.pulse()
 
2073
                return True
 
2074
 
 
2075
                self.do_convert()
 
2076
                return False
 
2077
                        
 
2078
 
 
2079
        def on_convert_button_clicked(self, *args):
 
2080
                if self._lock_convert_button:
 
2081
                        return
 
2082
 
 
2083
                if not self.converter.is_running():
 
2084
                        self.set_status(_("Waiting for tags"))
 
2085
                        self.progressframe.show()
 
2086
                        self.statusframe.hide()
 
2087
                        self.progress_time = time.time()
 
2088
                        #self.widget.set_sensitive(False)
 
2089
                
 
2090
                        self.convertion_waiting = True
 
2091
                        self.set_status(_("Waiting for tags..."))
 
2092
                
 
2093
                        #thread.start_thread(self.do_convert, ())
 
2094
                        self.do_convert()
 
2095
                        #gobject.timeout_add(100, self.wait_tags_and_convert)
 
2096
                else:
 
2097
                        self.converter.paused = not self.converter.paused
 
2098
                        if self.converter.paused:
 
2099
                                self.set_status(_("Paused"))
 
2100
                        else: 
 
2101
                                self.set_status("") 
 
2102
                self.set_sensitive()
 
2103
 
 
2104
        def on_button_pause_clicked(self, *args):
 
2105
                task = self.converter.get_current_task()
 
2106
                if task:
 
2107
                        self.converter.paused = not self.converter.paused
 
2108
                        task.toggle_pause(self.converter.paused)
 
2109
                else:
 
2110
                        return
 
2111
                if self.converter.paused:
 
2112
                        self.display_progress(_("Paused"))
 
2113
 
 
2114
        def on_button_cancel_clicked(self, *args):
 
2115
                self.converter.stop()
 
2116
                self.set_status(_("Canceled")) 
 
2117
                self.set_sensitive()
 
2118
                self.conversion_ended()
 
2119
 
 
2120
        def on_select_all_activate(self, *args):
 
2121
                self.filelist.widget.get_selection().select_all()
 
2122
                
 
2123
        def on_clear_activate(self, *args):
 
2124
                self.filelist.widget.get_selection().unselect_all()
 
2125
 
 
2126
        def on_preferences_activate(self, *args):
 
2127
                self.prefs.run()
 
2128
                
 
2129
        on_prefs_button_clicked = on_preferences_activate
 
2130
 
 
2131
        def on_about_activate(self, *args):
 
2132
                about = gtk.glade.XML(GLADE, "about").get_widget("about")
 
2133
                about.set_property("name", NAME)
 
2134
                about.set_property("version", VERSION)
 
2135
                about.set_property("translator_credits", TRANSLATORS)
 
2136
                about.show()
 
2137
 
 
2138
        def selection_changed(self, *args):
 
2139
                self.set_sensitive()
 
2140
 
 
2141
        def conversion_ended(self):
 
2142
                self.progressframe.hide()
 
2143
                self.statusframe.show()
 
2144
                self.widget.set_sensitive(True)
 
2145
 
 
2146
        def set_widget_sensitive(self, name, sensitivity):
 
2147
                self.sensitive_widgets[name].set_sensitive(sensitivity)
 
2148
 
 
2149
        def set_sensitive(self):
 
2150
 
 
2151
                [self.set_widget_sensitive(w, not self.converter.is_running()) 
 
2152
                        for w in self.unsensitive_when_converting]
 
2153
 
 
2154
                self.set_widget_sensitive("remove", 
 
2155
                        self.filelist_selection.count_selected_rows() > 0)
 
2156
                self.set_widget_sensitive("convert_button", 
 
2157
                                                                  self.filelist.is_nonempty())
 
2158
 
 
2159
                self._lock_convert_button = True
 
2160
                self.sensitive_widgets["convert_button"].set_active(
 
2161
                        self.converter.is_running() and not self.converter.paused )
 
2162
                self._lock_convert_button = False
 
2163
        
 
2164
        def display_progress(self, remaining):
 
2165
                self.progressbar.set_text(_("Converting file %d of %d  (%s)") % ( self.converter.tasks_current+1, self.converter.tasks_number, remaining ))
 
2166
        
 
2167
        def set_progress(self, done_so_far, total, current_file=""):
 
2168
                if (total==0) or (done_so_far==0):
 
2169
                        self.progressbar.set_text(" ")
 
2170
                        self.progressbar.set_fraction(0.0)
 
2171
                        self.progressbar.pulse()
 
2172
                        return
 
2173
                if time.time() < self.progress_time + 0.10:
 
2174
                        # ten updates per second should be enough
 
2175
                        return
 
2176
                self.progress_time = time.time()
 
2177
                
 
2178
                self.set_status(_("Converting"))
 
2179
                
 
2180
                self.progressfile.set_markup("<i><small>%s</small></i>" % current_file)
 
2181
                fraction = float(done_so_far) / total
 
2182
                
 
2183
                self.progressbar.set_fraction( min(fraction, 1.0) )
 
2184
                t = time.time() - self.converter.run_start_time - self.converter.paused_time
 
2185
                
 
2186
                if (t<1):
 
2187
                        # wait a bit not to display crap
 
2188
                        self.progressbar.pulse()
 
2189
                        return
 
2190
                        
 
2191
                r = (t / fraction - t)
 
2192
                #return
 
2193
                s = r%60
 
2194
                m = r/60
 
2195
                remaining = _("%d:%02d left") % (m,s)
 
2196
                self.display_progress(remaining)
 
2197
 
 
2198
        def set_status(self, text=None):
 
2199
                if not text:
 
2200
                        text = _("Ready")
 
2201
                self.status.set_markup(text)
 
2202
 
 
2203
 
 
2204
def gui_main(input_files):
 
2205
        gnome.init(NAME, VERSION)
 
2206
        glade = gtk.glade.XML(GLADE)
 
2207
        win = SoundConverterWindow(glade)
 
2208
        global error
 
2209
        error = ErrorDialog(glade)
 
2210
        #TODO
 
2211
        gobject.idle_add(win.filelist.add_uris, input_files)
 
2212
        win.set_sensitive()
 
2213
        #gtk.threads_enter()
 
2214
        gtk.main()
 
2215
        #gtk.threads_leave()
 
2216
 
 
2217
def cli_tags_main(input_files):
 
2218
        global error
 
2219
        error = ErrorPrinter()
 
2220
        for input_file in input_files:
 
2221
                if not get_option("quiet"):
 
2222
                        print input_file.get_uri()
 
2223
                t = TagReader(input_file)
 
2224
                t.setup()
 
2225
                while t.do_work():
 
2226
                        pass
 
2227
                t.finish()
 
2228
                if not get_option("quiet"):
 
2229
                        keys = input_file.keys()
 
2230
                        keys.sort()
 
2231
                        for key in keys:
 
2232
                                print "  %s: %s" % (key, input_file[key])
 
2233
 
 
2234
 
 
2235
class CliProgress:
 
2236
 
 
2237
        def __init__(self):
 
2238
                self.current_text = ""
 
2239
                
 
2240
        def show(self, new_text):
 
2241
                if new_text != self.current_text:
 
2242
                        self.clear()
 
2243
                        sys.stdout.write(new_text)
 
2244
                        sys.stdout.flush()
 
2245
                        self.current_text = new_text
 
2246
        
 
2247
        def clear(self):
 
2248
                sys.stdout.write("\b \b" * len(self.current_text))
 
2249
                sys.stdout.flush()
 
2250
 
 
2251
 
 
2252
def cli_convert_main(input_files):
 
2253
        global error
 
2254
        error = ErrorPrinter()
 
2255
 
 
2256
        output_type = get_option("cli-output-type")
 
2257
        output_suffix = get_option("cli-output-suffix")
 
2258
        
 
2259
        generator = TargetNameGenerator()
 
2260
        generator.set_target_suffix(output_suffix)
 
2261
        
 
2262
        progress = CliProgress()
 
2263
        
 
2264
        queue = TaskQueue()
 
2265
        for input_file in input_files:
 
2266
                input_file = SoundFile(input_file)
 
2267
                output_name = generator.get_target_name(input_file)
 
2268
                c = Converter(input_file, output_name, output_type)
 
2269
                c.overwrite = True
 
2270
                c.init()
 
2271
                queue.add(c)
 
2272
 
 
2273
        previous_filename = None
 
2274
        queue.run()
 
2275
        while queue.is_running():
 
2276
                t = queue.get_current_task()
 
2277
                if not get_option("quiet"):
 
2278
                        if previous_filename != t.sound_file.get_filename_for_display():
 
2279
                                if previous_filename:
 
2280
                                        print _("%s: OK") % previous_filename
 
2281
                                previous_filename = t.sound_file.get_filename_for_display()
 
2282
 
 
2283
                        percent = 0
 
2284
                        if t.get_duration():
 
2285
                                percent = "%.1f %%" % ( 100.0* (t.get_position() / t.get_duration() ))
 
2286
                        else:
 
2287
                                percent = "/-\|" [int(time.time()) % 4]
 
2288
                        progress.show("%s: %s" % (t.sound_file.get_filename_for_display()[-65:], percent ))
 
2289
                gtk_sleep(0.1)
 
2290
 
 
2291
        if not get_option("quiet"):
 
2292
                progress.clear()
 
2293
 
 
2294
 
 
2295
settings = {
 
2296
        "mode": "gui",
 
2297
        "quiet": False,
 
2298
        "debug": False,
 
2299
        "cli-output-type": "audio/x-vorbis",
 
2300
        "cli-output-suffix": ".ogg",
 
2301
}
 
2302
 
 
2303
 
 
2304
def set_option(key, value):
 
2305
        assert key in settings
 
2306
        settings[key] = value
 
2307
 
 
2308
 
 
2309
def get_option(key):
 
2310
        assert key in settings
 
2311
        return settings[key]
 
2312
 
 
2313
 
 
2314
def print_help(*args):
 
2315
        print _("Usage: %s [options] [soundfile ...]") % sys.argv[0]
 
2316
        for short_arg, long_arg, func, doc in options:
 
2317
                print
 
2318
                if short_arg[-1] == ":":
 
2319
                        print "  -%s arg, --%sarg" % (short_arg[:1], long_arg)
 
2320
                else:
 
2321
                        print "  -%s, --%s" % (short_arg[:1], long_arg)
 
2322
                for line in textwrap.wrap(doc):
 
2323
                        print "    %s" % line
 
2324
        sys.exit(0)
 
2325
 
 
2326
 
 
2327
options = [
 
2328
 
 
2329
        ("h", "help", print_help,
 
2330
         _("Print out a usage summary.")),
 
2331
 
 
2332
        ("b", "batch", lambda optarg: set_option("mode", "batch"),
 
2333
         _("Convert in batch mode, from command line, without a graphical user\n interface. You can use this from, say, shell scripts.")),
 
2334
 
 
2335
        ("m:", "mime-type=", lambda optarg: set_option("cli-output-type", optarg),
 
2336
         _("Set the output MIME type for batch mode. The default is\n %s . Note that you probably want to set\n the output suffix as well.") % get_option("cli-output-type")),
 
2337
         
 
2338
        ("q", "quiet", lambda optarg: set_option("quiet", True),
 
2339
         _("Be quiet. Don't write normal output, only errors.")),
 
2340
 
 
2341
        ("d", "debug", lambda optarg: set_option("debug", True),
 
2342
         _("Print additionnal debug information")),
 
2343
 
 
2344
        ("s:", "suffix=", lambda optarg: set_option("cli-output-suffix", optarg),
 
2345
         _("Set the output filename suffix for batch mode. The default is \n %s . Note that the suffix does not affect\n the output MIME type.") % get_option("cli-output-suffix")),
 
2346
 
 
2347
        ("t", "tags", lambda optarg: set_option("mode", "tags"),
 
2348
         _("Show tags for input files instead of converting them. This indicates \n command line batch mode and disables the graphical user interface.")),
 
2349
 
 
2350
        ]
 
2351
 
 
2352
 
 
2353
def main():
 
2354
        shortopts = "".join(map(lambda opt: opt[0], options))
 
2355
        longopts = map(lambda opt: opt[1], options)
 
2356
        
 
2357
        opts, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
 
2358
        for opt, optarg in opts:
 
2359
                for tuple in options:
 
2360
                        short = "-" + tuple[0][:1]
 
2361
                        long = "--" + tuple[1]
 
2362
                        if long.endswith("="):
 
2363
                                long = long[:-1]
 
2364
                        if opt in [short, long]:
 
2365
                                tuple[2](optarg)
 
2366
                                break
 
2367
        if 0:
 
2368
                print
 
2369
                for key in settings:
 
2370
                        print key, settings[key]
 
2371
                return
 
2372
        
 
2373
        args = map(filename_to_uri, args)
 
2374
 
 
2375
        if get_option("mode") == "gui":
 
2376
                gui_main(args)
 
2377
        elif get_option("mode") == "tags":
 
2378
                cli_tags_main(args)
 
2379
        else:
 
2380
                cli_convert_main(args)
 
2381
 
 
2382
 
 
2383
if __name__ == "__main__":
 
2384
        main()