def request_update(self, iter, task=None): if task is None: # Ongoing update request from UI - get task from model task = self.get_value(iter, self.C_TASK) else: # Initial update request - update non-changing fields self.set(iter, self.C_TASK, task, self.C_URL, task.url) if task.status == task.FAILED: status_message = '%s: %s' % (\ task.STATUS_MESSAGE[task.status], \ task.error_message) elif task.status == task.DOWNLOADING: status_message = '%s (%s, %s/s)' % (\ task.STATUS_MESSAGE[task.status], \ util.format_filesize(task.total_size), \ util.format_filesize(task.speed)) else: status_message = '%s (%s)' % (\ task.STATUS_MESSAGE[task.status], \ util.format_filesize(task.total_size)) self.set(iter, self.C_NAME, self._format_message(task.markup_name, \ status_message, task.markup_podcast_name), self.C_PROGRESS, 100.*task.progress, self.C_PROGRESS_TEXT, '%.0f%%' % (task.progress*100.,), self.C_ICON_NAME, self._status_ids[task.status])
def add_track(self, episode): self.notify('status', _('Adding %s...') % episode.title) filename = str(self.convert_track(episode)) logger.info("sending %s (%s).", filename, episode.title) try: # verify free space needed = util.calculate_size(filename) free = self.get_free_space() if needed > free: logger.error('Not enough space on device %s: %s available, but ' 'need at least %s', self.get_name(), util.format_filesize(free), util.format_filesize(needed)) self.cancelled = True return False # fill metadata metadata = pymtp.LIBMTP_Track() metadata.title = str(episode.title) metadata.artist = str(episode.channel.title) metadata.album = str(episode.channel.title) metadata.genre = "podcast" metadata.date = self.__date_to_mtp(episode.published) metadata.duration = get_track_length(str(filename)) folder_name = '' if episode.mimetype.startswith('audio/') and self._config.mtp_audio_folder: folder_name = self._config.mtp_audio_folder if episode.mimetype.startswith('video/') and self._config.mtp_video_folder: folder_name = self._config.mtp_video_folder if episode.mimetype.startswith('image/') and self._config.mtp_image_folder: folder_name = self._config.mtp_image_folder if folder_name != '' and self._config.mtp_podcast_folders: folder_name += os.path.sep + str(episode.channel.title) # log('Target MTP folder: %s' % folder_name) if folder_name == '': folder_id = 0 else: folder_id = self.__MTPDevice.mkdir(folder_name) # send the file to_file = util.sanitize_filename(metadata.title) + episode.extension() self.__MTPDevice.send_track_from_file(filename, to_file, metadata, folder_id, callback=self.__callback) if gpodder.user_hooks is not None: gpodder.user_hooks.on_file_copied_to_mtp(self, filename, to_file) except: logger.error('unable to add episode %s', episode.title) return False return True
def calculate_total_size(self): if self.size_attribute is not None: (total_size, count) = (0, 0) for episode in self.get_selected_episodes(): try: total_size += int(getattr(episode, self.size_attribute)) count += 1 except: pass text = [] if count == 0: text.append(_("Nothing selected")) text.append(N_("%(count)d episode", "%(count)d episodes", count) % {"count": count}) if total_size > 0: text.append(_("size: %s") % util.format_filesize(total_size)) self.labelTotalSize.set_text(", ".join(text)) self.btnOK.set_sensitive(count > 0) self.btnRemoveAction.set_sensitive(count > 0) if count > 0: self.btnCancel.set_label(gtk.STOCK_CANCEL) else: self.btnCancel.set_label(gtk.STOCK_CLOSE) else: self.btnOK.set_sensitive(False) self.btnRemoveAction.set_sensitive(False) for index, row in enumerate(self.model): if self.model.get_value(row.iter, self.COLUMN_TOGGLE) == True: self.btnOK.set_sensitive(True) self.btnRemoveAction.set_sensitive(True) break self.labelTotalSize.set_text("")
def on_download_status_progress(self): if self.task: self.download_progress.set_fraction(self.task.progress) self.download_progress.set_text('%s: %d%% (%s/s)' % ( \ self.task.STATUS_MESSAGE[self.task.status], \ 100.*self.task.progress, \ util.format_filesize(self.task.speed)))
def update(self, episode): self.scrolled_window.get_vadjustment().set_value(0) self.define_colors() if episode.has_website_link(): self._base_uri = episode.link else: self._base_uri = episode.channel.url # for incomplete base URI (e.g. http://919.noagendanotes.com) baseURI = urlparse(self._base_uri) if baseURI.path == '': self._base_uri += '/' self._loaded = False stylesheet = self.get_stylesheet() if stylesheet: self.manager.add_style_sheet(stylesheet) heading = '<h3>%s</h3>' % html.escape(episode.title) subheading = _('from %s') % html.escape(episode.channel.title) details = '<small>%s</small>' % html.escape(self.details_fmt % { 'date': util.format_date(episode.published), 'size': util.format_filesize(episode.file_size, digits=1) if episode.file_size > 0 else "-", 'duration': episode.get_play_info_string()}) header_html = _('<div id="gpodder-title">\n%(heading)s\n<p>%(subheading)s</p>\n<p>%(details)s</p></div>\n') \ % dict(heading=heading, subheading=subheading, details=details) # uncomment to prevent background override in html shownotes # self.manager.remove_all_style_sheets () logger.debug("base uri: %s (chan:%s)", self._base_uri, episode.channel.url) self.html_view.load_html(header_html + episode.html_description(), self._base_uri) # uncomment to show web inspector # self.html_view.get_inspector().show() self.episode = episode
def calculate_total_size( self): if self.size_attribute is not None: (total_size, count) = (0, 0) for episode in self.get_selected_episodes(): try: total_size += int(getattr( episode, self.size_attribute)) count += 1 except: log( 'Cannot get size for %s', episode.title, sender = self) text = [] if count == 0: text.append(_('Nothing selected')) else: text.append(N_('%(count)d episode', '%(count)d episodes', count) % {'count':count}) if total_size > 0: text.append(_('size: %s') % util.format_filesize(total_size)) self.labelTotalSize.set_text(', '.join(text)) self.btnOK.set_sensitive(count>0) self.btnRemoveAction.set_sensitive(count>0) else: selection = self.treeviewEpisodes.get_selection() selected_rows = selection.count_selected_rows() self.btnOK.set_sensitive(selected_rows > 0) self.btnRemoveAction.set_sensitive(selected_rows > 0) self.labelTotalSize.set_text('')
def update(self, episode): heading = episode.title subheading = _('from %s') % (episode.channel.title) details = self.details_fmt % ( util.format_date(episode.published), util.format_filesize(episode.file_size, digits=1) if episode.file_size > 0 else "-", episode.get_play_info_string()) self.define_colors() hyperlinks = [(0, None)] self.text_buffer.set_text('') self.text_buffer.insert_with_tags_by_name( self.text_buffer.get_end_iter(), heading, 'heading') self.text_buffer.insert_at_cursor('\n') self.text_buffer.insert_with_tags_by_name( self.text_buffer.get_end_iter(), subheading, 'subheading') self.text_buffer.insert_at_cursor('\n') self.text_buffer.insert_with_tags_by_name( self.text_buffer.get_end_iter(), details, 'details') self.text_buffer.insert_at_cursor('\n\n') for target, text in util.extract_hyperlinked_text( episode.description_html or episode.description): hyperlinks.append((self.text_buffer.get_char_count(), target)) if target: self.text_buffer.insert_with_tags_by_name( self.text_buffer.get_end_iter(), text, 'hyperlink') else: self.text_buffer.insert(self.text_buffer.get_end_iter(), text) hyperlinks.append((self.text_buffer.get_char_count(), None)) self.hyperlinks = [ (start, end, url) for (start, url), (end, _) in zip(hyperlinks, hyperlinks[1:]) if url ] self.text_buffer.place_cursor(self.text_buffer.get_start_iter())
def calculate_total_size(self): if self.size_attribute is not None: (total_size, count) = (0, 0) for episode in self.get_selected_episodes(): try: total_size += int(getattr(episode, self.size_attribute)) count += 1 except: pass text = [] if count == 0: text.append(_('Nothing selected')) text.append( N_('%(count)d episode', '%(count)d episodes', count) % {'count': count}) if total_size > 0: text.append(_('size: %s') % util.format_filesize(total_size)) self.labelTotalSize.set_text(', '.join(text)) self.btnOK.set_sensitive(count > 0) self.btnRemoveAction.set_sensitive(count > 0) if count > 0: self.btnCancel.set_label(gtk.STOCK_CANCEL) else: self.btnCancel.set_label(gtk.STOCK_CLOSE) else: self.btnOK.set_sensitive(False) self.btnRemoveAction.set_sensitive(False) for index, row in enumerate(self.model): if self.model.get_value(row.iter, self.COLUMN_TOGGLE) == True: self.btnOK.set_sensitive(True) self.btnRemoveAction.set_sensitive(True) break self.labelTotalSize.set_text('')
def calculate_total_size( self): if self.size_attribute is not None: (total_size, count) = (0, 0) for episode in self.get_selected_episodes(): try: total_size += int(getattr( episode, self.size_attribute)) count += 1 except: log( 'Cannot get size for %s', episode.title, sender = self) text = [] if count == 0: text.append(_('Nothing selected')) else: text.append(N_('%(count)d episode', '%(count)d episodes', count) % {'count':count}) if total_size > 0: text.append(_('size: %s') % util.format_filesize(total_size)) self.labelTotalSize.set_text(', '.join(text)) self.btnOK.set_sensitive(count>0) self.btnRemoveAction.set_sensitive(count>0) if count > 0: self.btnCancel.set_label(gtk.STOCK_CANCEL) else: self.btnCancel.set_label(gtk.STOCK_CLOSE) else: self.btnOK.set_sensitive(False) self.btnRemoveAction.set_sensitive(False) for index, row in enumerate(self.model): if self.model.get_value(row.iter, self.COLUMN_TOGGLE) == True: self.btnOK.set_sensitive(True) self.btnRemoveAction.set_sensitive(True) break self.labelTotalSize.set_text('')
def calculate_total_size( self): if self.size_attribute is not None: (total_size, count) = (0, 0) for episode in self.get_selected_episodes(): try: total_size += int(getattr( episode, self.size_attribute)) count += 1 except: log( 'Cannot get size for %s', episode.title, sender = self) text = [] if count == 0: text.append(_('Nothing selected')) else: text.append(N_('%d episode', '%d episodes', count) % count) if total_size > 0: text.append(_('size: %s') % util.format_filesize(total_size)) self.labelTotalSize.set_text(', '.join(text)) self.btnOK.set_sensitive(count>0) self.btnRemoveAction.set_sensitive(count>0) else: selection = self.treeviewEpisodes.get_selection() selected_rows = selection.count_selected_rows() self.btnOK.set_sensitive(selected_rows > 0) self.btnRemoveAction.set_sensitive(selected_rows > 0) self.labelTotalSize.set_text('')
def request_update(self, iter, task=None): if task is None: # Ongoing update request from UI - get task from model task = self.get_value(iter, self.C_TASK) else: # Initial update request - update non-changing fields self.set(iter, self.C_TASK, task, self.C_URL, task.url) if task.status == task.FAILED: status_message = "%s: %s" % (task.STATUS_MESSAGE[task.status], task.error_message) elif task.status == task.DOWNLOADING: status_message = "%s (%.0f%%, %s/s)" % ( task.STATUS_MESSAGE[task.status], task.progress * 100, util.format_filesize(task.speed), ) else: status_message = task.STATUS_MESSAGE[task.status] if task.progress > 0 and task.progress < 1: current = util.format_filesize(task.progress * task.total_size, digits=1) total = util.format_filesize(task.total_size, digits=1) # Remove unit from current if same as in total # (does: "12 MiB / 24 MiB" => "12 / 24 MiB") current = current.split() if current[-1] == total.split()[-1]: current.pop() current = " ".join(current) progress_message = " / ".join((current, total)) elif task.total_size > 0: progress_message = util.format_filesize(task.total_size, digits=1) else: progress_message = "unknown size" self.set( iter, self.C_NAME, self._format_message(task.episode.title, status_message, task.episode.channel.title), self.C_PROGRESS, 100.0 * task.progress, self.C_PROGRESS_TEXT, progress_message, self.C_ICON_NAME, self._status_ids[task.status], )
def add_track(self, episode, reporthook=None): self.notify('status', _('Adding %s') % episode.title.decode('utf-8', 'ignore')) # get the folder on the device folder = self.get_episode_folder_on_device(episode) filename = episode.local_filename(create=False) # The file has to exist, if we ought to transfer it, and therefore, # local_filename(create=False) must never return None as filename assert filename is not None from_file = util.sanitize_encoding(filename) # verify free space needed = util.calculate_size(from_file) free = self.get_free_space() if free == -1: logger.warn('Cannot determine free disk space on device') elif needed > free: d = { 'path': self.destination, 'free': util.format_filesize(free), 'need': util.format_filesize(needed) } message = _( 'Not enough space in %(path)s: %(free)s available, but need at least %(need)s' ) raise SyncFailedException(message % d) # get the filename that will be used on the device to_file = self.get_episode_file_on_device(episode) to_file = util.sanitize_encoding(os.path.join(folder, to_file)) if not os.path.exists(folder): try: os.makedirs(folder) except: logger.error('Cannot create folder on MP3 player: %s', folder) return False if not os.path.exists(to_file): logger.info('Copying %s => %s', os.path.basename(from_file), to_file.decode(util.encoding)) self.copy_file_progress(from_file, to_file, reporthook) return True
def request_update(self, iter, task=None): if task is None: # Ongoing update request from UI - get task from model task = self.get_value(iter, self.C_TASK) else: # Initial update request - update non-changing fields self.set(iter, self.C_TASK, task, self.C_URL, task.url) if task.status == task.FAILED: status_message = '%s: %s' % ( task.STATUS_MESSAGE[task.status], task.error_message) elif task.status == task.DOWNLOADING: status_message = '%s (%.0f%%, %s/s)' % ( task.STATUS_MESSAGE[task.status], task.progress * 100, util.format_filesize(task.speed)) else: status_message = task.STATUS_MESSAGE[task.status] if task.progress > 0 and task.progress < 1: current = util.format_filesize(task.progress * task.total_size, digits=1) total = util.format_filesize(task.total_size, digits=1) # Remove unit from current if same as in total # (does: "12 MiB / 24 MiB" => "12 / 24 MiB") current = current.split() if current[-1] == total.split()[-1]: current.pop() current = ' '.join(current) progress_message = ' / '.join((current, total)) elif task.total_size > 0: progress_message = util.format_filesize(task.total_size, digits=1) else: progress_message = ('unknown size') self.set(iter, self.C_NAME, self._format_message(task.episode.title, status_message, task.episode.channel.title), self.C_PROGRESS, 100. * task.progress, self.C_PROGRESS_TEXT, progress_message, self.C_ICON_NAME, self._status_ids[task.status])
def on_download_status_progress(self): # We receive this from the main window every time the progress # for our episode has changed (but only when this window is visible) if self.task: self.download_progress.set_fraction(self.task.progress) self.download_progress.set_text('%s: %d%% (%s/s)' % ( \ self.task.STATUS_MESSAGE[self.task.status], \ 100.*self.task.progress, \ util.format_filesize(self.task.speed)))
def markup_new_episodes(self): if self.file_size > 0: length_str = '%s; ' % util.format_filesize(self.file_size) else: length_str = '' return ('<b>%s</b>\n<small>%s' + _('released %s') + '; ' + _('from %s') + '</small>') % ( html.escape(re.sub('\s+', ' ', self.title)), html.escape(length_str), html.escape(self.pubdate_prop), html.escape(re.sub('\s+', ' ', self.channel.title)))
def on_download_status_progress(self): if self.task: self.download_progress.set_fraction(self.task.progress) self.download_progress.set_text( "%s: %d%% (%s/s)" % ( self.task.STATUS_MESSAGE[self.task.status], 100.0 * self.task.progress, util.format_filesize(self.task.speed), ) )
def markup_new_episodes(self): if self.file_size > 0: length_str = '%s; ' % util.format_filesize(self.file_size) else: length_str = '' return ('<b>%s</b>\n<small>%s' + _('released %s') + '; ' + _('from %s') + '</small>') % ( cgi.escape(re.sub('\s+', ' ', self.title)), cgi.escape(length_str), cgi.escape(self.pubdate_prop), cgi.escape(re.sub('\s+', ' ', self.channel.title)))
def __init__(self, title, length, modified, **kwargs): self.title = title self.length = length self.filesize = util.format_filesize(length) self.modified = modified # Set some (possible) keyword arguments to default values self.playcount = 0 self.podcast = None # Convert keyword arguments to object attributes self.__dict__.update(kwargs)
def __init__(self, title, length, modified, **kwargs): self.title = title self.length = length self.filesize = util.format_filesize(length) self.modified = modified # Set some (possible) keyword arguments to default values self.playcount = 0 self.podcast = None self.released = None # Convert keyword arguments to object attributes self.__dict__.update(kwargs)
def add_track(self, task, reporthook=None): episode = task.episode self.notify('status', _('Adding %s') % episode.title) # get the folder on the device folder = self.get_episode_folder_on_device(episode) filename = episode.local_filename(create=False) # The file has to exist, if we ought to transfer it, and therefore, # local_filename(create=False) must never return None as filename assert filename is not None from_file = filename # verify free space needed = util.calculate_size(from_file) free = self.get_free_space() if free == -1: logger.warn('Cannot determine free disk space on device') elif needed > free: d = {'path': self.destination, 'free': util.format_filesize(free), 'need': util.format_filesize(needed)} message = _('Not enough space in %(path)s: %(free)s available, but need at least %(need)s') raise SyncFailedException(message % d) # get the filename that will be used on the device to_file = self.get_episode_file_on_device(episode) to_file = folder.get_child(to_file) util.make_directory(folder) if not to_file.query_exists(): logger.info('Copying %s => %s', os.path.basename(from_file), to_file.get_uri()) from_file = Gio.File.new_for_path(from_file) try: def hookconvert(current_bytes, total_bytes, user_data): return reporthook(current_bytes, 1, total_bytes) from_file.copy(to_file, Gio.FileCopyFlags.OVERWRITE, task.cancellable, hookconvert, None) except GLib.Error as err: if err.matches(Gio.io_error_quark(), Gio.IOErrorEnum.CANCELLED): raise SyncCancelledException() logger.error('Error copying %s to %s: %s', from_file.get_uri(), to_file.get_uri(), err.message) d = {'from_file': from_file.get_uri(), 'to_file': to_file.get_uri(), 'message': err.message} self.errors.append(_('Error copying %(from_file)s to %(to_file)s: %(message)s') % d) return False return True
def markup_delete_episodes(self): if self.total_time and self.current_position: played_string = self.get_play_info_string() elif not self.is_new: played_string = _('played') else: played_string = _('unplayed') downloaded_string = self.get_age_string() if not downloaded_string: downloaded_string = _('today') return ('<b>%s</b>\n<small>%s; %s; ' + _('downloaded %s') + '; ' + _('from %s') + '</small>') % ( html.escape(self.title), html.escape(util.format_filesize(self.file_size)), html.escape(played_string), html.escape(downloaded_string), html.escape(self.channel.title))
def markup_delete_episodes(self): if self.total_time and self.current_position: played_string = self.get_play_info_string() elif not self.is_new: played_string = _('played') else: played_string = _('unplayed') downloaded_string = self.get_age_string() if not downloaded_string: downloaded_string = _('today') return ('<b>%s</b>\n<small>%s; %s; ' + _('downloaded %s') + '; ' + _('from %s') + '</small>') % ( cgi.escape(self.title), cgi.escape(util.format_filesize(self.file_size)), cgi.escape(played_string), cgi.escape(downloaded_string), cgi.escape(self.channel.title))
def check_free_space(): # "Will we add this episode to the device?" def will_add(episode): # If already on-device, it won't take up any space if device.episode_on_device(episode): return False # Might not be synced if it's played already if not force_played and \ self._config.only_sync_not_played and \ episode.is_played: return False # In all other cases, we expect the episode to be # synchronized to the device, so "answer" positive return True # "What is the file size of this episode?" def file_size(episode): filename = episode.local_filename(create=False) if filename is None: return 0 return util.calculate_size(str(filename)) # Calculate total size of sync and free space on device total_size = sum(file_size(e) for e in episodes if will_add(e)) free_space = max(device.get_free_space(), 0) if total_size > free_space: title = _('Not enough space left on device') message = _('You need to free up %s.\nDo you want to continue?') \ % (util.format_filesize(total_size-free_space),) if not self.show_confirmation(message, title): device.cancel() device.close() return # Finally start the synchronization process gPodderSyncProgress(self.parent_window, device=device) def sync_thread_func(): device.add_tracks(episodes, force_played=force_played) device.close() threading.Thread(target=sync_thread_func).start()
def check_free_space(): # "Will we add this episode to the device?" def will_add(episode): # If already on-device, it won't take up any space if device.episode_on_device(episode): return False # Might not be synced if it's played already if (not force_played and self._config.device_sync.skip_played_episodes): return False # In all other cases, we expect the episode to be # synchronized to the device, so "answer" positive return True # "What is the file size of this episode?" def file_size(episode): filename = episode.local_filename(create=False) if filename is None: return 0 return util.calculate_size(str(filename)) # Calculate total size of sync and free space on device total_size = sum(file_size(e) for e in episodes if will_add(e)) free_space = max(device.get_free_space(), 0) if total_size > free_space: title = _('Not enough space left on device') message = (_( 'Additional free space required: %(required_space)s\nDo you want to continue?' ) % { 'required_space': util.format_filesize(total_size - free_space) }) if not self.show_confirmation(message, title): device.cancel() device.close() return # Finally start the synchronization process @util.run_in_background def sync_thread_func(): self.enable_download_list_update() device.add_sync_tasks(episodes, force_played=force_played)
def check_free_space(): # "Will we add this episode to the device?" def will_add(episode): # If already on-device, it won't take up any space if device.episode_on_device(episode): return False # Might not be synced if it's played already if (not force_played and self._config.device_sync.skip_played_episodes): return False # In all other cases, we expect the episode to be # synchronized to the device, so "answer" positive return True # "What is the file size of this episode?" def file_size(episode): filename = episode.local_filename(create=False) if filename is None: return 0 return util.calculate_size(str(filename)) # Calculate total size of sync and free space on device total_size = sum(file_size(e) for e in episodes if will_add(e)) free_space = max(device.get_free_space(), 0) if total_size > free_space: title = _('Not enough space left on device') message = (_('Additional free space required: %(required_space)s\nDo you want to continue?') % {'required_space': util.format_filesize(total_size - free_space)}) if not self.show_confirmation(message, title): device.cancel() device.close() return # Finally start the synchronization process @util.run_in_background def sync_thread_func(): self.enable_download_list_update() device.add_sync_tasks(episodes, force_played=force_played)
def add_track(self, episode,reporthook=None): self.notify('status', _('Adding %s') % episode.title.decode('utf-8', 'ignore')) # get the folder on the device folder = self.get_episode_folder_on_device(episode) filename = episode.local_filename(create=False) # The file has to exist, if we ought to transfer it, and therefore, # local_filename(create=False) must never return None as filename assert filename is not None from_file = util.sanitize_encoding(filename) # verify free space needed = util.calculate_size(from_file) free = self.get_free_space() if free == -1: logger.warn('Cannot determine free disk space on device') elif needed > free: d = {'path': self.destination, 'free': util.format_filesize(free), 'need': util.format_filesize(needed)} message =_('Not enough space in %(path)s: %(free)s available, but need at least %(need)s') raise SyncFailedException(message % d) # get the filename that will be used on the device to_file = self.get_episode_file_on_device(episode) to_file = util.sanitize_encoding(os.path.join(folder, to_file)) if not os.path.exists(folder): try: os.makedirs(folder) except: logger.error('Cannot create folder on MP3 player: %s', folder) return False if not os.path.exists(to_file): logger.info('Copying %s => %s', os.path.basename(from_file), to_file.decode(util.encoding)) self.copy_file_progress(from_file, to_file, reporthook) return True
def update(self, episode): self.scrolled_window.get_vadjustment().set_value(0) heading = episode.title subheading = _('from %s') % (episode.channel.title) details = self.details_fmt % { 'date': util.format_date(episode.published), 'size': util.format_filesize(episode.file_size, digits=1) if episode.file_size > 0 else "-", 'duration': episode.get_play_info_string()} self.define_colors() hyperlinks = [(0, None)] self.text_buffer.set_text('') if episode.link: hyperlinks.append((self.text_buffer.get_char_count(), episode.link)) self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(), heading, 'heading') if episode.link: hyperlinks.append((self.text_buffer.get_char_count(), None)) self.text_buffer.insert_at_cursor('\n') self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(), subheading, 'subheading') self.text_buffer.insert_at_cursor('\n') self.text_buffer.insert_with_tags_by_name(self.text_buffer.get_end_iter(), details, 'details') self.text_buffer.insert_at_cursor('\n\n') for target, text in util.extract_hyperlinked_text(episode.html_description()): hyperlinks.append((self.text_buffer.get_char_count(), target)) if target: self.text_buffer.insert_with_tags_by_name( self.text_buffer.get_end_iter(), text, 'hyperlink') else: self.text_buffer.insert( self.text_buffer.get_end_iter(), text) hyperlinks.append((self.text_buffer.get_char_count(), None)) self.hyperlinks = [(start, end, url) for (start, url), (end, _) in zip(hyperlinks, hyperlinks[1:]) if url] self.text_buffer.place_cursor(self.text_buffer.get_start_iter()) if self.populate_popup_id is not None: self.text_view.disconnect(self.populate_popup_id) self.populate_popup_id = self.text_view.connect('populate-popup', self.on_populate_popup) self.episode = episode
def _format_filesize(self, episode): if episode.length > 0: return util.format_filesize(episode.length, 1) else: return None
def _filesize(self): if self._episode.file_size: return util.format_filesize(self._episode.file_size) else: return ''
def format_filesize(self, bytesize, digits=2): return util.format_filesize(bytesize, self.config.use_si_units, digits)
def check_free_space(): # "Will we add this episode to the device?" def will_add(episode): # If already on-device, it won't take up any space if device.episode_on_device(episode): return False # Might not be synced if it's played already if (not force_played and self._config.device_sync.skip_played_episodes): return False # In all other cases, we expect the episode to be # synchronized to the device, so "answer" positive return True # "What is the file size of this episode?" def file_size(episode): filename = episode.local_filename(create=False) if filename is None: return 0 return util.calculate_size(str(filename)) # Calculate total size of sync and free space on device total_size = sum(file_size(e) for e in episodes if will_add(e)) free_space = max(device.get_free_space(), 0) if total_size > free_space: title = _('Not enough space left on device') message = (_('Additional free space required: %(required_space)s\nDo you want to continue?') % {'required_space': util.format_filesize(total_size - free_space)}) if not self.show_confirmation(message, title): device.cancel() device.close() return #enable updating of UI self.enable_download_list_update() #Update device playlists #General approach is as follows: #When a episode is downloaded and synched, it is added to the #standard playlist for that podcast which is then written to #the device. #After the user has played that episode on their device, they #can delete that episode from their device. #At the next sync, gPodder will then compare the standard #podcast-specific playlists on the device (as written by #gPodder during the last sync), with the episodes on the #device.If there is an episode referenced in the playlist #that is no longer on the device, gPodder will assume that #the episode has already been synced and subsequently deleted #from the device, and will hence mark that episode as deleted #in gPodder. If there are no playlists, nothing is deleted. #At the next sync, the playlists will be refreshed based on #the downloaded, undeleted episodes in gPodder, and the #cycle begins again... def resume_sync(episode_urls, channel_urls,progress): if progress is not None: progress.on_finished() #rest of sync process should continue here self.commit_changes_to_database() for current_channel in self.channels: #only sync those channels marked for syncing if (self._config.device_sync.device_type=='filesystem' and current_channel.sync_to_mp3_player and self._config.device_sync.playlists.create): #get playlist object playlist=gPodderDevicePlaylist(self._config, current_channel.title) #need to refresh episode list so that #deleted episodes aren't included in playlists episodes_for_playlist=sorted(current_channel.get_episodes(gpodder.STATE_DOWNLOADED), key=lambda ep: ep.published) #don't add played episodes to playlist if skip_played_episodes is True if self._config.device_sync.skip_played_episodes: episodes_for_playlist=filter(lambda ep: ep.is_new, episodes_for_playlist) playlist.write_m3u(episodes_for_playlist) #enable updating of UI self.enable_download_list_update() if (self._config.device_sync.device_type=='filesystem' and self._config.device_sync.playlists.create): title = _('Update successful') message = _('The playlist on your MP3 player has been updated.') self.notification(message, title, widget=self.preferences_widget) # Finally start the synchronization process @util.run_in_background def sync_thread_func(): device.add_sync_tasks(episodes, force_played=force_played, done_callback=self.enable_download_list_update) return if self._config.device_sync.playlists.create: try: episodes_to_delete=[] if self._config.device_sync.playlists.two_way_sync: for current_channel in self.channels: #only include channels that are included in the sync if current_channel.sync_to_mp3_player: #get playlist object playlist=gPodderDevicePlaylist(self._config, current_channel.title) #get episodes to be written to playlist episodes_for_playlist=sorted(current_channel.get_episodes(gpodder.STATE_DOWNLOADED), key=lambda ep: ep.published) episode_keys=map(playlist.get_absolute_filename_for_playlist, episodes_for_playlist) episode_dict=dict(zip(episode_keys, episodes_for_playlist)) #then get episodes in playlist (if it exists) already on device episodes_in_playlists = playlist.read_m3u() #if playlist doesn't exist (yet) episodes_in_playlist will be empty if episodes_in_playlists: for episode_filename in episodes_in_playlists: if not(os.path.exists(os.path.join(playlist.mountpoint, episode_filename))): #episode was synced but no longer on device #i.e. must have been deleted by user, so delete from gpodder try: episodes_to_delete.append(episode_dict[episode_filename]) except KeyError, ioe: logger.warn('Episode %s, removed from device has already been deleted from gpodder', episode_filename) #delete all episodes from gpodder (will prompt user) #not using playlists to delete def auto_delete_callback(episodes): if not episodes: #episodes were deleted on device #but user decided not to delete them from gpodder #so jump straight to sync logger.info ('Starting sync - no episodes selected for deletion') resume_sync([],[],None) else: #episodes need to be deleted from gpodder for episode_to_delete in episodes: logger.info("Deleting episode %s", episode_to_delete.title) logger.info ('Will start sync - after deleting episodes') self.delete_episode_list(episodes,False, True,resume_sync) return if episodes_to_delete: columns = ( ('markup_delete_episodes', None, None, _('Episode')), ) gPodderEpisodeSelector(self.parent_window, title = _('Episodes have been deleted on device'), instructions = 'Select the episodes you want to delete:', episodes = episodes_to_delete, selected = [True,]*len(episodes_to_delete), columns = columns, callback = auto_delete_callback, _config=self._config) else: logger.warning("Starting sync - no episodes to delete") resume_sync([],[],None) except IOError, ioe: title = _('Error writing playlist files') message = _(str(ioe)) self.notification(message, title, widget=self.preferences_widget)
def _format_filesize(self, episode): if episode.file_size > 0: return util.format_filesize(episode.file_size, digits=1) else: return None
def add_track(self, episode, reporthook=None): self.notify('status', _('Adding %s') % episode.title) # episode.description # episode.description_html # file = episode.local_filename(create=False) # episode.link # episode.title # cover # #bb =io.BytesIO(episode.channel.cover) folder = self.get_episode_folder_on_device(episode) filename = episode.local_filename(create=False) # The file has to exist, if we ought to transfer it, and therefore, # local_filename(create=False) must never return None as filename assert filename is not None from_file = filename # verify free space needed = util.calculate_size(from_file) free = self.get_free_space() if free == -1: logger.warn('Cannot determine free disk space on device') elif needed > free: d = { 'path': self.destination, 'free': util.format_filesize(free), 'need': util.format_filesize(needed) } message = _( 'Not enough space in %(path)s: %(free)s available, but need at least %(need)s' ) raise SyncFailedException(message % d) # get the filename that will be used on the device to_file = self.get_episode_file_on_device(episode) to_file_name = to_file to_file = os.path.join(folder, to_file) video_file = to_file.replace(".mp3", "").replace(".m4a", "") + ".avi" cmd_file = to_file.replace(".mp3", "").replace(".m4a", "") + ".ps1" image_file = to_file.replace(".mp3", "").replace(".m4a", "") + ".png" jpg_file = episode.channel.cover_file + ".jpg" if not os.path.exists(image_file): img = Image.open(jpg_file) draw = ImageDraw.Draw(img) font = ImageFont.truetype( "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 72) draw.text((0, 0), episode.title, (255, 255, 255), font=font) font = ImageFont.truetype( "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 42) yy = draw.multiline_text((0, 120), episode.description, (255, 255, 255), font=font) img.save(image_file) # write the image if not os.path.exists(folder): try: os.makedirs(folder) except: logger.error('Cannot create folder on MP3 player: %s', folder) return False if not os.path.exists(to_file): logger.info('Copying %s => %s', os.path.basename(from_file), to_file) self.copy_file_progress(from_file, to_file, reporthook) #ffmpeg -i "$1" -acodec libfaac -ac 2 -ab 128k -s 480x320 #-vcodec libx264 -vpre libx264-hq -vpre libx264-ipod640 -b 768k -bt 512k -aspect 3:2 -threads 0 -f mp4 $1.mp4 cmd = [ 'ffmpeg', '-framerate', '2', '-loop', '1', '-i', image_file, "-i", to_file, "-qmin", "3", "-qmax", "5", "-ac", "2", "-ab", "128k", "-b:v", "512k", "-aspect", "3:2", "-threads", "4", "-s", "480x320", "-ar", "16000", "-b:a", "44K", "-vbr", "constrained", "-vf", "scale=iw/4:ih/4", "-profile:v", "baseline", "-preset", "ultrafast", "-movflags", "+faststart", "-crf", "16", "-c:v", "libx264", "-c:a", "libmp3lame", "-reset_timestamps", "1", "-tune", "stillimage", "-pix_fmt", "yuv420p", "-shortest", video_file #"'{}'".format(video_file) ] #ffmpeg -i test.mpg -c:v libx264 test.avi cmd = subprocess.list2cmdline(cmd) if not os.path.exists(cmd_file): with open(cmd_file, "w") as fo: fo.write(cmd.replace(folder + "/", "")) if not os.path.exists(video_file): check_call(shlex.split(cmd)) # now upload #loop = asyncio.get_event_loop() #loop.run_until_complete(run_bcupload(video_file, image_file, episode)) #loop.close() return True
def add_track(self, episode): self.notify('status', _('Adding %s...') % episode.title) filename = str(self.convert_track(episode)) logger.info("sending %s (%s).", filename, episode.title) try: # verify free space needed = util.calculate_size(filename) free = self.get_free_space() if needed > free: logger.error( 'Not enough space on device %s: %s available, but need at least %s', self.get_name(), util.format_filesize(free), util.format_filesize(needed)) self.cancelled = True return False # fill metadata metadata = pymtp.LIBMTP_Track() metadata.title = str(episode.title) metadata.artist = str(episode.channel.title) metadata.album = str(episode.channel.title) metadata.genre = "podcast" metadata.date = self.__date_to_mtp(episode.published) metadata.duration = get_track_length(str(filename)) folder_name = '' if episode.mimetype.startswith( 'audio/') and self._config.mtp_audio_folder: folder_name = self._config.mtp_audio_folder if episode.mimetype.startswith( 'video/') and self._config.mtp_video_folder: folder_name = self._config.mtp_video_folder if episode.mimetype.startswith( 'image/') and self._config.mtp_image_folder: folder_name = self._config.mtp_image_folder if folder_name != '' and self._config.mtp_podcast_folders: folder_name += os.path.sep + str(episode.channel.title) # log('Target MTP folder: %s' % folder_name) if folder_name == '': folder_id = 0 else: folder_id = self.__MTPDevice.mkdir(folder_name) # send the file to_file = util.sanitize_filename( metadata.title) + episode.extension() self.__MTPDevice.send_track_from_file(filename, to_file, metadata, folder_id, callback=self.__callback) if gpodder.user_hooks is not None: gpodder.user_hooks.on_file_copied_to_mtp( self, filename, to_file) except: logger.error('unable to add episode %s', episode.title) return False return True
def get_filesize_string(self): return util.format_filesize(self.length)
def check_free_space(): # "Will we add this episode to the device?" def will_add(episode): # If already on-device, it won't take up any space if device.episode_on_device(episode): return False # Might not be synced if it's played already if (not force_played and self._config.device_sync.skip_played_episodes): return False # In all other cases, we expect the episode to be # synchronized to the device, so "answer" positive return True # "What is the file size of this episode?" def file_size(episode): filename = episode.local_filename(create=False) if filename is None: return 0 return util.calculate_size(str(filename)) # Calculate total size of sync and free space on device total_size = sum(file_size(e) for e in episodes if will_add(e)) free_space = max(device.get_free_space(), 0) if total_size > free_space: title = _('Not enough space left on device') message = (_('Additional free space required: %(required_space)s\nDo you want to continue?') % {'required_space': util.format_filesize(total_size - free_space)}) if not self.show_confirmation(message, title): device.cancel() device.close() return #enable updating of UI self.enable_download_list_update() #Update device playlists #General approach is as follows: #When a episode is downloaded and synched, it is added to the #standard playlist for that podcast which is then written to #the device. #After the user has played that episode on their device, they #can delete that episode from their device. #At the next sync, gPodder will then compare the standard #podcast-specific playlists on the device (as written by #gPodder during the last sync), with the episodes on the #device.If there is an episode referenced in the playlist #that is no longer on the device, gPodder will assume that #the episode has already been synced and subsequently deleted #from the device, and will hence mark that episode as deleted #in gPodder. If there are no playlists, nothing is deleted. #At the next sync, the playlists will be refreshed based on #the downloaded, undeleted episodes in gPodder, and the #cycle begins again... def resume_sync(episode_urls, channel_urls,progress): if progress is not None: progress.on_finished() #rest of sync process should continue here self.commit_changes_to_database() for current_channel in self.channels: #only sync those channels marked for syncing if (self._config.device_sync.device_type=='filesystem' and current_channel.sync_to_mp3_player and self._config.device_sync.playlists.create): #get playlist object playlist=gPodderDevicePlaylist(self._config, current_channel.title) #need to refresh episode list so that #deleted episodes aren't included in playlists episodes_for_playlist=sorted(current_channel.get_episodes(gpodder.STATE_DOWNLOADED), key=lambda ep: ep.published) #don't add played episodes to playlist if skip_played_episodes is True if self._config.device_sync.skip_played_episodes: episodes_for_playlist=[ep for ep in episodes_for_playlist if ep.is_new] playlist.write_m3u(episodes_for_playlist) #enable updating of UI self.enable_download_list_update() if (self._config.device_sync.device_type=='filesystem' and self._config.device_sync.playlists.create): title = _('Update successful') message = _('The playlist on your MP3 player has been updated.') self.notification(message, title) # Finally start the synchronization process @util.run_in_background def sync_thread_func(): device.add_sync_tasks(episodes, force_played=force_played, done_callback=self.enable_download_list_update) return if self._config.device_sync.playlists.create: try: episodes_to_delete=[] if self._config.device_sync.playlists.two_way_sync: for current_channel in self.channels: #only include channels that are included in the sync if current_channel.sync_to_mp3_player: #get playlist object playlist=gPodderDevicePlaylist(self._config, current_channel.title) #get episodes to be written to playlist episodes_for_playlist=sorted(current_channel.get_episodes(gpodder.STATE_DOWNLOADED), key=lambda ep: ep.published) episode_keys=list(map(playlist.get_absolute_filename_for_playlist, episodes_for_playlist)) episode_dict=dict(list(zip(episode_keys, episodes_for_playlist))) #then get episodes in playlist (if it exists) already on device episodes_in_playlists = playlist.read_m3u() #if playlist doesn't exist (yet) episodes_in_playlist will be empty if episodes_in_playlists: for episode_filename in episodes_in_playlists: if not(os.path.exists(os.path.join(playlist.mountpoint, episode_filename))): #episode was synced but no longer on device #i.e. must have been deleted by user, so delete from gpodder try: episodes_to_delete.append(episode_dict[episode_filename]) except KeyError as ioe: logger.warn('Episode %s, removed from device has already been deleted from gpodder', episode_filename) #delete all episodes from gpodder (will prompt user) #not using playlists to delete def auto_delete_callback(episodes): if not episodes: #episodes were deleted on device #but user decided not to delete them from gpodder #so jump straight to sync logger.info('Starting sync - no episodes selected for deletion') resume_sync([],[],None) else: #episodes need to be deleted from gpodder for episode_to_delete in episodes: logger.info("Deleting episode %s", episode_to_delete.title) logger.info('Will start sync - after deleting episodes') self.delete_episode_list(episodes,False, True,resume_sync) return if episodes_to_delete: columns = ( ('markup_delete_episodes', None, None, _('Episode')), ) gPodderEpisodeSelector(self.parent_window, title = _('Episodes have been deleted on device'), instructions = 'Select the episodes you want to delete:', episodes = episodes_to_delete, selected = [True,] * len(episodes_to_delete), columns = columns, callback = auto_delete_callback, _config=self._config) else: logger.warning("Starting sync - no episodes to delete") resume_sync([],[],None) except IOError as ioe: title = _('Error writing playlist files') message = _(str(ioe)) self.notification(message, title) else: logger.info('Not creating playlists - starting sync') resume_sync([],[],None)
def add_track(self, episode): self.notify('status', _('Adding %s...') % episode.title) filename = str(self.convert_track(episode)) log("sending " + filename + " (" + episode.title + ").", sender=self) try: # verify free space needed = util.calculate_size(filename) free = self.get_free_space() if needed > free: log('Not enough space on device %s: %s available, but need at least %s', self.get_name(), util.format_filesize(free), util.format_filesize(needed), sender=self) self.cancelled = True return False # fill metadata metadata = pymtp.LIBMTP_Track() metadata.title = str(episode.title) metadata.artist = str(episode.channel.title) metadata.album = str(episode.channel.title) metadata.genre = "podcast" metadata.date = self.__date_to_mtp(episode.pubDate) metadata.duration = get_track_length(str(filename)) # send the file self.__MTPDevice.send_track_from_file(filename, util.sanitize_filename(metadata.title)+episode.extension(), metadata, 0, callback=self.__callback) except: log('unable to add episode %s', episode.title, sender=self, traceback=True) return False return True
def add_track(self, episode): self.notify('status', _('Adding %s') % episode.title) for track in gpod.sw_get_playlist_tracks(self.podcasts_playlist): if episode.url == track.podcasturl: if track.playcount > 0: gl.history_mark_played(track.podcasturl) # Mark as played on iPod if played locally (and set podcast flags) self.set_podcast_flags(track) return True original_filename=str(episode.local_filename()) local_filename=original_filename # Reserve 10 MiB for iTunesDB writing (to be on the safe side) RESERVED_FOR_ITDB=1024*1024*10 space_for_track=util.get_free_disk_space(self.mountpoint) - RESERVED_FOR_ITDB needed=util.calculate_size(local_filename) if needed > space_for_track: log('Not enough space on %s: %s available, but need at least %s', self.mountpoint, util.format_filesize(space_for_track), util.format_filesize(needed), sender=self) self.errors.append( _('Error copying %s: Not enough free disk space on %s') % (episode.title, self.mountpoint)) self.cancelled=True return False (fn, extension)=os.path.splitext(original_filename) if libconverter.converters.has_converter(extension): log('Converting: %s', original_filename, sender=self) callback_status=lambda percentage: self.notify('sub-progress', int(percentage)) local_filename=libconverter.converters.convert(original_filename, callback=callback_status) if not libtagupdate.update_metadata_on_file(local_filename, title=episode.title, artist=episode.channel.title): log('Could not set metadata on converted file %s', local_filename, sender=self) if local_filename is None: log('Cannot convert %s', original_filename, sender=self) return False else: local_filename=str(local_filename) (fn, extension)=os.path.splitext(local_filename) if extension.lower().endswith('ogg'): log('Cannot copy .ogg files to iPod.', sender=self) return False track=gpod.itdb_track_new() # Add release time to track if pubDate is parseable ipod_date=email.Utils.parsedate(episode.pubDate) if ipod_date is not None: try: # libgpod>= 0.5.x uses a new timestamp format track.time_released=gpod.itdb_time_host_to_mac(int(time.mktime(ipod_date))) except: # old (pre-0.5.x) libgpod versions expect mactime, so # we're going to manually build a good mactime timestamp here :) # # + 2082844800 for unixtime => mactime (1970 => 1904) track.time_released=int(time.mktime(ipod_date) + 2082844800) track.title=str(episode.title) track.album=str(episode.channel.title) track.artist=str(episode.channel.title) track.description=str(episode.description) track.podcasturl=str(episode.url) track.podcastrss=str(episode.channel.url) track.tracklen=get_track_length(local_filename) track.size=os.path.getsize(local_filename) if episode.file_type() == 'audio': track.filetype='mp3' track.mediatype=0x00000004 elif episode.file_type() == 'video': track.filetype='m4v' track.mediatype=0x00000006 self.set_podcast_flags(track) self.set_cover_art(track, local_filename) gpod.itdb_track_add(self.itdb, track, -1) gpod.itdb_playlist_add_track(self.podcasts_playlist, track, -1) gpod.itdb_cp_track_to_ipod( track, local_filename, None) # If the file has been converted, delete the temporary file here if local_filename != original_filename: util.delete_file(local_filename) return True