1# Copyright (C) 2009-2010 Aren Olson
2#
3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation; either version 2, or (at your option)
6# any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program; if not, write to the Free Software
15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16#
17#
18# The developers of the Exaile media player hereby grant permission
19# for non-GPL compatible GStreamer and Exaile plugins to be used and
20# distributed together with GStreamer and Exaile. This permission is
21# above and beyond the permissions granted by the GPL license by which
22# Exaile is covered. If you modify this code, you may extend this
23# exception to your version of the code, but you are not obligated to
24# do so. If you do not wish to do so, delete this exception statement
25# from your version.
26
27import logging
28import threading
29import os
30
31from gi.repository import GLib
32
33from xl import common, event, formatter, settings, transcoder, trax
34from xl.nls import gettext as _
35from xlgui.panel import device
36import xlgui
37
38
39logger = logging.getLogger(__name__)
40
41
42class CDImportThread(common.ProgressThread):
43    def __init__(self, cd_importer):
44        common.ProgressThread.__init__(self)
45
46        self.cd_importer = cd_importer
47
48    def stop(self):
49        """
50        Stops the thread
51        """
52        self.cd_importer.stop()
53        common.ProgressThread.stop(self)
54
55    def on_progress_update(self, progress=None):
56        """
57        Notifies about progress changes
58        """
59        if progress is None:
60            progress = self.cd_importer.get_progress() * 100
61
62        self.emit('progress-update', int(progress))
63
64        return True
65
66    def run(self):
67        """
68        Runs the thread
69        """
70        progress_id = GLib.timeout_add_seconds(1, self.on_progress_update)
71        self.cd_importer.do_import()
72        GLib.source_remove(progress_id)
73        self.emit('done')
74
75
76class CDPanel(device.FlatPlaylistDevicePanel):
77    def __init__(self, *args):
78        device.FlatPlaylistDevicePanel.__init__(self, *args)
79        self.__importing = False
80
81        event.add_ui_callback(self._tree_queue_draw, 'cddb_info_retrieved')
82
83    def _tree_queue_draw(self, type, cdplaylist, object=None):
84        if not hasattr(self.fppanel, 'tree'):
85            return
86
87        if cdplaylist in self.device.playlists:
88            logger.info("Calling queue_draw for %s", str(cdplaylist))
89            self.fppanel.tree.queue_draw()
90
91    def do_import(self, tracks):
92        if self.__importing:
93            return
94        self.__importing = True
95        cd_importer = CDImporter(tracks)
96        thread = CDImportThread(cd_importer)
97        thread.connect('done', lambda *e: self._import_finish())
98        self.main.controller.progress_manager.add_monitor(
99            thread, _("Importing CD..."), 'drive-optical'
100        )
101
102    def _import_finish(self):
103        self.__importing = False
104
105
106class CDImporter:
107    def __init__(self, tracks):
108        self.tracks = [t for t in tracks if t.get_loc_for_io().startswith("cdda")]
109        self.duration = sum(t.get_tag_raw('__length') for t in self.tracks)
110        self.formatter = formatter.TrackFormatter(
111            settings.get_option(
112                "cd_import/outpath",
113                "%s/$artist/$album/$tracknumber - $title" % os.getenv("HOME"),
114            )
115        )
116        self.current = None
117        self.current_len = None
118        self.progress = 0.0
119
120        self.running = False
121
122        self.cont = None
123        self.__prepare_transcoder()
124
125    def __prepare_transcoder(self):
126        formats = transcoder.get_formats()
127        default_format = next(formats.keys())
128        self.format = settings.get_option("cd_import/format", default_format)
129        default_quality = formats[default_format]['default']
130        self.quality = settings.get_option("cd_import/quality", default_quality)
131        self.transcoder = transcoder.Transcoder(
132            self.format, self.quality, self._error_cb, self._end_cb
133        )
134
135    def do_import(self):
136        self.running = True
137
138        self.cont = threading.Event()
139
140        self.transcoder.set_format(self.format)
141        if self.quality != -1:
142            self.transcoder.set_quality(self.quality)
143
144        for tr in self.tracks:
145            self.cont.clear()
146            self.current = tr
147            self.current_len = tr.get_tag_raw('__length')
148            loc = tr.get_loc_for_io()
149            trackno, device = loc[7:].split("/#")
150            src = "cdparanoiasrc track=%s device=\"%s\"" % (trackno, device)
151            self.transcoder.set_raw_input(src)
152            outloc = self.get_output_location(tr)
153            self.transcoder.set_output(outloc)
154            self.transcoder.start_transcode()
155            self.cont.wait()
156            if not self.running:
157                break
158            tr2 = trax.Track("file://" + outloc)
159            ntags = {
160                t: tr.get_tag_raw(t) for t in tr.list_tags() if not t.startswith("__")
161            }
162            tr2.set_tags(**ntags)
163            tr2.write_tags()
164            try:
165                incr = tr.get_tag_raw('__length') / self.duration
166                self.progress += incr
167            except Exception:
168                raise
169        self.progress = 100.0
170
171    def _end_cb(self):
172        self.cont.set()
173        xlgui.main.mainwindow().message.show_info("Finished transcoding files")
174
175    def _error_cb(self, gerror, message_string):
176        self.running = False
177        xlgui.main.mainwindow().message.show_error(
178            _("Error transcoding files from CD."), "%s" % gerror.message.encode()
179        )
180
181    def get_output_location(self, track):
182        path = self.formatter.format(track)
183        directorypath = os.path.dirname(path)
184
185        if not os.path.exists(directorypath):
186            os.makedirs(directorypath)
187
188        extension = transcoder.FORMATS[self.transcoder.dest_format]['extension']
189
190        return path + '.' + extension
191
192    def stop(self):
193        self.running = False
194        self.transcoder.stop()
195
196    def get_progress(self):
197        if not self.current or not self.current_len:
198            return self.progress
199        incr = self.current_len / self.duration
200        pos = self.transcoder.get_time() / self.current_len
201        return self.progress + pos * incr
202