def _start_thumbnailing_cb(self): if not self.__start_id: # Can happen if stopGeneration is called because the clip has been # removed from the timeline after the PreviewGeneratorManager # started this job. return self.__start_id = None if isinstance(self.ges_elem, GES.ImageSource): self.debug("Generating thumbnail for image: %s", path_from_uri(self.uri)) self.__image_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( Gst.uri_get_location(self.uri), -1, self.thumb_height, True) self.thumb_width = self.__image_pixbuf.props.width self._update_thumbnails() self.emit("done") else: if not self.thumb_width: self.debug("Finding thumb width") self.pipeline = self._setup_pipeline() return # Update the thumbnails with what we already have, if anything. self._update_thumbnails() if self.queue: self.debug("Generating thumbnails for video: %s, %s", path_from_uri(self.uri), self.queue) # When the pipeline status is set to PAUSED, # the first thumbnail generation will be scheduled. self.pipeline = self._setup_pipeline() else: self.emit("done")
def _setScenarioFile(self, uri): if 'PITIVI_SCENARIO_FILE' in os.environ: uri = quote_uri(os.environ['PITIVI_SCENARIO_FILE']) else: cache_dir = get_dir(os.path.join(xdg_cache_home(), "scenarios")) scenario_name = str(time.strftime("%Y%m%d-%H%M%S")) project_path = None if uri: project_path = path_from_uri(uri) scenario_name += os.path.splitext( project_path.replace(os.sep, "_"))[0] uri = os.path.join(cache_dir, scenario_name + ".scenario") uri = quote_uri(uri) self._scenario_file = open(path_from_uri(uri), "w") if project_path: f = open(project_path) content = f.read() if not uri.endswith(".scenario"): self.write_action( "load-project", {"serialized-content": "%s" % content.replace("\n", "")}) f.close()
def _set_scenario_file(self, uri): if uri: project_path = path_from_uri(uri) else: # New project. project_path = None if 'PITIVI_SCENARIO_FILE' in os.environ: scenario_path = os.environ['PITIVI_SCENARIO_FILE'] else: cache_dir = xdg_cache_home("scenarios") scenario_name = str(time.strftime("%Y%m%d-%H%M%S")) if project_path: scenario_name += os.path.splitext( project_path.replace(os.sep, "_"))[0] scenario_path = os.path.join(cache_dir, scenario_name + ".scenario") scenario_path = path_from_uri(quote_uri(scenario_path)) self._scenario_file = open(scenario_path, "w") if project_path and not project_path.endswith(".scenario"): # It's an xges file probably. with open(project_path) as project: content = project.read().replace("\n", "") self.write_action("load-project", serialized_content=content)
def _start_thumbnailing_cb(self): if not self.__start_id: # Can happen if stopGeneration is called because the clip has been # removed from the timeline after the PreviewGeneratorManager # started this job. return self.__start_id = None if isinstance(self.ges_elem, GES.ImageSource): self.debug('Now generating thumbnail for: %s', path_from_uri(self.uri)) self.__image_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( Gst.uri_get_location(self.uri), -1, self.thumb_height, True) self.thumb_width = self.__image_pixbuf.props.width self.emit("done") else: self.debug('Now generating thumbnails for: %s', path_from_uri(self.uri)) self.pipeline = self._setup_pipeline() self._schedule_next_thumb_generation() # Update the thumbnails with what we already have, if anything. self._update_thumbnails() # Stop calling me. return False
def _schedule_next_thumb_generation(self): """Schedules the generation of the next thumbnail, or stop. Checks the CPU usage and adjusts the waiting time at which the next thumbnail will be generated +/- 10%. Even then, it will only happen when the gobject loop is idle to avoid blocking the UI. """ if self._thumb_cb_id is not None: # A thumb has already been scheduled. return if not self.queue: # Nothing left to do. self.debug("Thumbnails generation complete") self.stop_generation() return usage_percent = self.cpu_usage_tracker.usage() if usage_percent < self._max_cpu_usage: self.interval *= 0.9 self.log("Thumbnailing sped up to a %.1f ms interval for `%s`", self.interval, path_from_uri(self.uri)) else: self.interval *= 1.1 self.log("Thumbnailing slowed down to a %.1f ms interval for `%s`", self.interval, path_from_uri(self.uri)) self.cpu_usage_tracker.reset() self._thumb_cb_id = GLib.timeout_add(self.interval, self._create_next_thumb_cb, priority=GLib.PRIORITY_LOW)
def cloned_sample(*samples): """Gets a context manager which commits the transaction at the end.""" with tempfile.TemporaryDirectory() as tmpdir: module = globals() original_get_sample_uri = module["get_sample_uri"] module["get_sample_uri"] = lambda sample: original_get_sample_uri(sample, samples_dir=tmpdir) try: for sample in samples: sample_path = path_from_uri(original_get_sample_uri(sample)) clone_path = path_from_uri(get_sample_uri(sample)) shutil.copyfile(sample_path, clone_path) yield tmpdir finally: module["get_sample_uri"] = original_get_sample_uri
def beautify_info(info): """ Formats the specified info for display. @type info: L{DiscovererInfo} """ ranks = {DiscovererVideoInfo: 0, DiscovererAudioInfo: 1, DiscovererStreamInfo: 2} def stream_sort_key(stream): try: return ranks[type(stream)] except KeyError: return len(ranks) info.get_stream_list().sort(key=stream_sort_key) nice_streams_txts = [] for stream in info.get_stream_list(): try: beautified_string = beautify_stream(stream) except NotImplementedError: doLog(ERROR, "Beautify", "None", "Cannot beautify %s", stream) continue if beautified_string: nice_streams_txts.append(beautified_string) return "<b>" + path_from_uri(info.get_uri()) + "</b>\n" + "\n".join(nice_streams_txts)
def _getFilesizeEstimate(self): """Estimates the final file size. Estimates in megabytes (over 30 MB) are rounded to the nearest 10 MB to smooth out small variations. You'd be surprised how imprecision can improve perceived accuracy. Returns: str: A human-readable (ex: "14 MB") estimate for the file size. """ if not self.current_position or self.current_position == 0: return None current_filesize = os.stat(path_from_uri(self.outfile)).st_size length = self.project.ges_timeline.props.duration estimated_size = float( current_filesize * float(length) / self.current_position) # Now let's make it human-readable (instead of octets). # If it's in the giga range (10⁹) instead of mega (10⁶), use 2 decimals if estimated_size > 10e8: gigabytes = estimated_size / (10 ** 9) return _("%.2f GB" % gigabytes) else: megabytes = int(estimated_size / (10 ** 6)) if megabytes > 30: megabytes = int(round(megabytes, -1)) # -1 means round to 10 return _("%d MB" % megabytes)
def _getFilesizeEstimate(self): """ Using the current render output's filesize and position in the timeline, return a human-readable (ex: "14 MB") estimate of the final filesize. Estimates in megabytes (over 30 MB) are rounded to the nearest 10 MB to smooth out small variations. You'd be surprised how imprecision can improve perceived accuracy. """ if not self.current_position or self.current_position == 0: return None current_filesize = os.stat(path_from_uri(self.outfile)).st_size length = self.app.project_manager.current_project.timeline.props.duration estimated_size = float(current_filesize * float(length) / self.current_position) # Now let's make it human-readable (instead of octets). # If it's in the giga range (10⁹) instead of mega (10⁶), use 2 decimals if estimated_size > 10e8: gigabytes = estimated_size / (10**9) return _("%.2f GB" % gigabytes) else: megabytes = int(estimated_size / (10**6)) if megabytes > 30: megabytes = int(round(megabytes, -1)) # -1 means round to 10 return _("%d MB" % megabytes)
def _launchPipeline(self): self.debug('Now generating waveforms for: %s', path_from_uri(self._uri)) self.pipeline = Gst.parse_launch("uridecodebin name=decode uri=" + self._uri + " ! waveformbin name=wave" " ! fakesink qos=false name=faked") # This line is necessary so we can instantiate GstTranscoder's # GstCpuThrottlingClock below. Gst.ElementFactory.make("uritranscodebin", None) clock = GObject.new(GObject.type_from_name("GstCpuThrottlingClock")) clock.props.cpu_usage = self._max_cpu_usage self.pipeline.use_clock(clock) faked = self.pipeline.get_by_name("faked") faked.props.sync = True self._wavebin = self.pipeline.get_by_name("wave") asset = self.ges_elem.get_asset().get_filesource_asset() self._wavebin.props.uri = asset.get_id() self._wavebin.props.duration = asset.get_duration() decode = self.pipeline.get_by_name("decode") decode.connect("autoplug-select", self._autoplug_select_cb) bus = self.pipeline.get_bus() bus.add_signal_watch() self.n_samples = asset.get_duration() / SAMPLE_DURATION bus.connect("message", self._busMessageCb)
def _create_next_thumb(self): if not self.wishlist or not self.queue: # nothing left to do self.debug("Thumbnails generation complete") self.stopGeneration() self.thumb_cache.commit() return else: self.debug("Missing %d thumbs", len(self.wishlist)) wish = self._get_wish() if wish: time = wish self.queue.remove(wish) else: time = self.queue.pop(0) self.log('Creating thumb for "%s"', path_from_uri(self.uri)) # append the time to the end of the queue so that if this seek fails # another try will be started later self.queue.append(time) self.pipeline.seek(1.0, Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE, Gst.SeekType.SET, time, Gst.SeekType.NONE, -1) # Remove the GSource self._thumb_cb_id = None return False
def _cleanBackup(self, uri): if uri is None: return path = path_from_uri(self._makeBackupURI(uri)) if os.path.exists(path): os.remove(path) self.debug('Removed backup file "%s"' % path)
def _startThumbnailing(self): if not self.pipeline: # Can happen if stopGeneration is called because the clip has been # removed from the timeline after the PreviewGeneratorManager # started this job. return self.debug('Now generating thumbnails for: %s', path_from_uri(self.uri)) query_success, duration = self.pipeline.query_duration(Gst.Format.TIME) if not query_success or duration == -1: self.debug("Could not determine duration of: %s", self.uri) duration = self.ges_elem.props.duration self.queue = list(range(0, duration, self.thumb_period)) self._checkCPU() # Save periodically to avoid the common situation where the user exits # the app before a long clip has been fully thumbnailed. # Spread timeouts between 30-80 secs to avoid concurrent disk writes. random_time = random.randrange(30, 80) GLib.timeout_add_seconds(random_time, self._autosave) # Remove the GSource return False
def _startThumbnailing(self): if not self.pipeline: # Can happen if stopGeneration is called because the clip has been # removed from the timeline after the PreviewGeneratorManager # started this job. return self.debug( 'Now generating thumbnails for: %s', path_from_uri(self.uri)) query_success, duration = self.pipeline.query_duration(Gst.Format.TIME) if not query_success or duration == -1: self.debug("Could not determine duration of: %s", self.uri) duration = self.ges_elem.props.duration self.queue = list(range(0, duration, self.thumb_period)) self._checkCPU() # Save periodically to avoid the common situation where the user exits # the app before a long clip has been fully thumbnailed. # Spread timeouts between 30-80 secs to avoid concurrent disk writes. random_time = random.randrange(30, 80) GLib.timeout_add_seconds(random_time, self._autosave) # Remove the GSource return False
def _launchPipeline(self): self.debug( 'Now generating waveforms for: %s', path_from_uri(self._uri)) self.pipeline = Gst.parse_launch("uridecodebin name=decode uri=" + self._uri + " ! waveformbin name=wave" " ! fakesink qos=false name=faked") # This line is necessary so we can instantiate GstTranscoder's # GstCpuThrottlingClock below. Gst.ElementFactory.make("uritranscodebin", None) clock = GObject.new(GObject.type_from_name("GstCpuThrottlingClock")) clock.props.cpu_usage = self._max_cpu_usage self.pipeline.use_clock(clock) faked = self.pipeline.get_by_name("faked") faked.props.sync = True self._wavebin = self.pipeline.get_by_name("wave") asset = self.ges_elem.get_parent().get_asset() self._wavebin.props.uri = asset.get_id() self._wavebin.props.duration = asset.get_duration() decode = self.pipeline.get_by_name("decode") decode.connect("autoplug-select", self._autoplug_select_cb) bus = self.pipeline.get_bus() bus.add_signal_watch() asset = self.ges_elem.get_parent().get_asset() self.n_samples = asset.get_duration() / SAMPLE_DURATION bus.connect("message", self._busMessageCb)
def beautify_info(info): """ Formats the specified info for display. @type info: L{DiscovererInfo} """ ranks = { DiscovererVideoInfo: 0, DiscovererAudioInfo: 1, DiscovererStreamInfo: 2 } def stream_sort_key(stream): try: return ranks[type(stream)] except KeyError: return len(ranks) info.get_stream_list().sort(key=stream_sort_key) nice_streams_txts = [] for stream in info.get_stream_list(): try: beautified_string = beautify_stream(stream) except NotImplementedError: doLog(ERROR, "Beautify", "None", "Cannot beautify %s", stream) continue if beautified_string: nice_streams_txts.append(beautified_string) return ("<b>" + path_from_uri(info.get_uri()) + "</b>\n" + "\n".join(nice_streams_txts))
def _start_thumbnailing_cb(self): if not self.__start_id: # Can happen if stopGeneration is called because the clip has been # removed from the timeline after the PreviewGeneratorManager # started this job. return False self.__start_id = None if not self.thumb_width: self.debug("Finding thumb width") self._setup_pipeline() return False # Update the thumbnails with what we already have, if anything. self._update_thumbnails() if self.queue: self.debug("Generating thumbnails for video: %s, %s", path_from_uri(self.uri), self.queue) # When the pipeline status is set to PAUSED, # the first thumbnail generation will be scheduled. self._setup_pipeline() else: self.emit("done") # Stop calling me, I started already. return False
def _project_manager_new_project_loaded_cb(self, project_manager, project): """Connects the UI to the specified project. Args: project_manager (ProjectManager): The project manager. project (Project): The project which has been loaded. """ self.log("A new project has been loaded") self._connect_to_project(project) project.pipeline.activate_position_listener() self.viewer.set_project(project) self.clipconfig.set_project(project, self.timeline_ui) self.timeline_ui.set_project(project) # When creating a blank project there's no project URI yet. if project.uri: folder_path = os.path.dirname(path_from_uri(project.uri)) self.settings.lastProjectFolder = folder_path self.update_title() if project_manager.disable_save is True: # Special case: we enforce "Save as", but the normal "Save" button # redirects to it if needed, so we still want it to be enabled: self.save_action.set_enabled(True) if project.ges_timeline.props.duration != 0: self.render_button.set_sensitive(True)
def loadProject(self, uri): """ Load the given URI as a project. If a backup file exists, ask if it should be loaded instead, and if so, force the user to use "Save as" afterwards. """ if self.current is not None and not self.closeRunningProject(): return False self.emit("new-project-loading", uri) # We really want a path for os.path to work path = path_from_uri(uri) backup_path = self._makeBackupURI(path_from_uri(uri)) use_backup = False try: time_diff = os.path.getmtime(backup_path) - os.path.getmtime(path) self.debug('Backup file "%s" is %d secs newer' % (backup_path, time_diff)) except OSError: self.debug('Backup file "%s" does not exist' % backup_path) else: if time_diff > 0: use_backup = self._restoreFromBackupDialog(time_diff) if use_backup: uri = self._makeBackupURI(uri) self.debug('Loading project from backup "%s"' % uri) # Make a new project instance, but don't specify the URI. # That way, we force the user to "Save as" (which ensures that the # changes in the loaded backup file are approved by the user). self.current = Project() else: # Load the project normally. # The "old" backup file will eventually be deleted or overwritten. self.current = Project(uri=uri) self.emit("new-project-created", self.current) timeline = self.current.timeline self.formatter = GES.PitiviFormatter() self.formatter.connect("source-moved", self._formatterMissingURICb) self.formatter.connect("loaded", self._projectLoadedCb) if self.formatter.load_from_uri(timeline, uri): self.current.connect("project-changed", self._projectChangedCb) return True self.warn("Could not load project %s", uri) return False
def _addAsset(self, asset): # 128 is the normal size for thumbnails, but for *icons* it looks insane LARGE_SIZE = 96 info = asset.get_info() # The code below tries to read existing thumbnails from the freedesktop # thumbnails directory (~/.thumbnails). The filenames are simply # the file URI hashed with md5, so we can retrieve them easily. video_streams = [i for i in info.get_stream_list() if isinstance(i, DiscovererVideoInfo)] if len(video_streams) > 0: # From the freedesktop spec: "if the environment variable # $XDG_CACHE_HOME is set and not blank then the directory # $XDG_CACHE_HOME/thumbnails will be used, otherwise # $HOME/.cache/thumbnails will be used." # Older version of the spec also mentioned $HOME/.thumbnails quoted_uri = quote_uri(info.get_uri()) thumbnail_hash = md5(quoted_uri).hexdigest() try: thumb_dir = os.environ['XDG_CACHE_HOME'] thumb_64, thumb_128 = self._getThumbnailInDir(thumb_dir, thumbnail_hash) except KeyError: thumb_64, thumb_128 = (None, None) if thumb_64 is None: thumb_dir = os.path.expanduser("~/.cache/thumbnails/") thumb_64, thumb_128 = self._getThumbnailInDir(thumb_dir, thumbnail_hash) if thumb_64 is None: thumb_dir = os.path.expanduser("~/.thumbnails/") thumb_64, thumb_128 = self._getThumbnailInDir(thumb_dir, thumbnail_hash) if thumb_64 is None: if asset.is_image(): thumb_64 = self._getIcon("image-x-generic") thumb_128 = self._getIcon("image-x-generic", None, LARGE_SIZE) else: thumb_64 = self._getIcon("video-x-generic") thumb_128 = self._getIcon("video-x-generic", None, LARGE_SIZE) # TODO ideally gst discoverer should create missing thumbnails. self.log("Missing a thumbnail for %s, queuing", path_from_uri(quoted_uri)) self._missing_thumbs.append(quoted_uri) else: thumb_64 = self._getIcon("audio-x-generic") thumb_128 = self._getIcon("audio-x-generic", None, LARGE_SIZE) if info.get_duration() == Gst.CLOCK_TIME_NONE: duration = '' else: duration = beautify_length(info.get_duration()) name = info_name(info) self.pending_rows.append((thumb_64, thumb_128, beautify_info(info), asset, info.get_uri(), duration, name)) if len(self.pending_rows) > 50: self.flush_pending_rows()
def test_backup_project(self): self.manager.new_blank_project() # Assign an uri to the project where it's saved by default. unused, xges_path = tempfile.mkstemp(suffix=".xges") uri = "file://" + os.path.abspath(xges_path) self.manager.current_project.uri = uri # This is where the automatic backup file is saved. backup_uri = self.manager._make_backup_uri(uri) # Save the backup self.assertTrue(self.manager.save_project( self.manager.current_project, backup=True)) self.assertTrue(os.path.isfile(path_from_uri(backup_uri))) self.manager.close_running_project() self.assertFalse(os.path.isfile(path_from_uri(backup_uri)), "Backup file not deleted when project closed")
def testBackupProject(self): self.manager.new_blank_project() # Assign an uri to the project where it's saved by default. unused, xges_path = tempfile.mkstemp(suffix=".xges") uri = "file://" + os.path.abspath(xges_path) self.manager.current_project.uri = uri # This is where the automatic backup file is saved. backup_uri = self.manager._makeBackupURI(uri) # Save the backup self.assertTrue(self.manager.saveProject( self.manager.current_project, backup=True)) self.assertTrue(os.path.isfile(path_from_uri(backup_uri))) self.manager.closeRunningProject() self.assertFalse(os.path.isfile(path_from_uri(backup_uri)), "Backup file not deleted when project closed")
def _schedule_next_thumb_generation(self): """Schedules the generation of the next thumbnail. Checks the CPU usage and adjusts the waiting time at which the next thumbnail will be generated +/- 10%. Even then, it will only happen when the gobject loop is idle to avoid blocking the UI. """ usage_percent = self.cpu_usage_tracker.usage() if usage_percent < self._max_cpu_usage: self.interval *= 0.9 self.log("Thumbnailing sped up to a %.1f ms interval for `%s`", self.interval, path_from_uri(self.uri)) else: self.interval *= 1.1 self.log("Thumbnailing slowed down to a %.1f ms interval for `%s`", self.interval, path_from_uri(self.uri)) self.cpu_usage_tracker.reset() self._thumb_cb_id = GLib.timeout_add(self.interval, self._create_next_thumb_cb, priority=GLib.PRIORITY_LOW)
def _allSourcesInHomedir(self, sources): """ Checks if all sources are located in the users home directory """ homedir = os.path.expanduser("~") for source in sources: if not path_from_uri(source.get_uri()).startswith(homedir): return False return True
def exportProject(self, project, uri): """ Export a project to a *.tar archive which includes the project file and all sources """ # write project file to temporary file project_name = project.name if project.name else "project" tmp_name = "%s.xptv" % project_name try: directory = os.path.dirname(uri) tmp_uri = os.path.join(directory, tmp_name) self.saveProject(project, tmp_uri, overwrite=True) # create tar file with tarfile.open(path_from_uri(uri), mode="w") as tar: # top directory in tar-file top = "%s-export" % project_name # add temporary project file tar.add(path_from_uri(tmp_uri), os.path.join(top, tmp_name)) # get common path sources = project.medialibrary.getSources() if self._allSourcesInHomedir(sources): common = os.path.expanduser("~") else: common = "/" # add all sources for source in sources: path = path_from_uri(source.get_uri()) tar.add(path, os.path.join(top, os.path.relpath(path, common))) tar.close() # remove temporary file os.remove(path_from_uri(tmp_uri)) except: return False return True
def _checkCPU(self): """Adjusts when the next thumbnail is generated. Checks the CPU usage and adjusts the waiting time at which the next thumbnail will be generated +/- 10%. Even then, it will only happen when the gobject loop is idle to avoid blocking the UI. """ usage_percent = self.cpu_usage_tracker.usage() if usage_percent < THUMBNAILS_CPU_USAGE: self.interval *= 0.9 self.log( 'Thumbnailing sped up (+10%%) to a %.1f ms interval for "%s"', self.interval, path_from_uri(self.uri)) else: self.interval *= 1.1 self.log( 'Thumbnailing slowed down (-10%%) to a %.1f ms interval for "%s"', self.interval, path_from_uri(self.uri)) self.cpu_usage_tracker.reset() self._thumb_cb_id = GLib.timeout_add(self.interval, self._create_next_thumb, priority=GLib.PRIORITY_LOW)
def __init__(self, uri): Loggable.__init__(self) # TODO: replace with utils.misc.hash_file self._filehash = hash_file(Gst.uri_get_location(uri)) self._filename = os.path.basename(path_from_uri(uri)) # TODO: replace with pitivi.settings.xdg_cache_home() cache_dir = get_dir(os.path.join(xdg_dirs.xdg_cache_home, "pitivi"), autocreate) dbfile = os.path.join(get_dir(os.path.join(cache_dir, "thumbs")), self._filehash) self._db = sqlite3.connect(dbfile) self._cur = self._db.cursor() # Use this for normal db operations self._cur.execute("CREATE TABLE IF NOT EXISTS Thumbs\ (Time INTEGER NOT NULL PRIMARY KEY,\ Jpeg BLOB NOT NULL)")
def _checkCPU(self): """Adjusts when the next thumbnail is generated. Checks the CPU usage and adjusts the waiting time at which the next thumbnail will be generated +/- 10%. Even then, it will only happen when the gobject loop is idle to avoid blocking the UI. """ usage_percent = self.cpu_usage_tracker.usage() if usage_percent < self._max_cpu_usage: self.interval *= 0.9 self.log( 'Thumbnailing sped up (+10%%) to a %.1f ms interval for "%s"', self.interval, path_from_uri(self.uri)) else: self.interval *= 1.1 self.log( 'Thumbnailing slowed down (-10%%) to a %.1f ms interval for "%s"', self.interval, path_from_uri(self.uri)) self.cpu_usage_tracker.reset() self._thumb_cb_id = GLib.timeout_add(self.interval, self._create_next_thumb, priority=GLib.PRIORITY_LOW)
def _setScenarioFile(self, uri): if 'PITIVI_SCENARIO_FILE' in os.environ: uri = quote_uri(os.environ['PITIVI_SCENARIO_FILE']) else: cache_dir = get_dir(os.path.join(xdg_cache_home(), "scenarios")) scenario_name = str(time.strftime("%Y%m%d-%H%M%S")) project_path = None if uri: project_path = path_from_uri(uri) scenario_name += os.path.splitext(project_path.replace(os.sep, "_"))[0] uri = os.path.join(cache_dir, scenario_name + ".scenario") uri = quote_uri(uri) self._scenario_file = open(path_from_uri(uri), "w") if project_path: f = open(project_path) content = f.read() self.write_action("load-project", {"serialized-content": "%s" % content.replace("\n", "")}) f.close()
def _setScenarioFile(self, uri): if uri: project_path = path_from_uri(uri) else: # New project. project_path = None if 'PITIVI_SCENARIO_FILE' in os.environ: scenario_path = os.environ['PITIVI_SCENARIO_FILE'] else: cache_dir = get_dir(os.path.join(xdg_cache_home(), "scenarios")) scenario_name = str(time.strftime("%Y%m%d-%H%M%S")) if project_path: scenario_name += os.path.splitext(project_path.replace(os.sep, "_"))[0] scenario_path = os.path.join(cache_dir, scenario_name + ".scenario") scenario_path = path_from_uri(quote_uri(scenario_path)) self._scenario_file = open(scenario_path, "w") if project_path and not project_path.endswith(".scenario"): # It's an xges file probably. with open(project_path) as project: content = project.read().replace("\n", "") self.write_action("load-project", serialized_content=content)
def _setProject(self, project): """Disconnects and then reconnects callbacks to the specified project. Args: project (Project): The new current project. """ if not project: self.warning("Current project instance does not exist") return False self.clipconfig.project = project # When creating a blank project there's no project URI yet. if project.uri: folder_path = os.path.dirname(path_from_uri(project.uri)) self.settings.lastProjectFolder = folder_path
def beautify_asset(asset): """Formats the specified asset for display. Args: asset (GES.Asset): The asset to display. """ from pitivi.utils.proxy import get_proxy_target uri = get_proxy_target(asset).props.id path = path_from_uri(uri) res = ["<b>" + GLib.markup_escape_text(path) + "</b>"] ranks = { DiscovererVideoInfo: 0, DiscovererAudioInfo: 1, DiscovererStreamInfo: 2 } def stream_sort_key(stream): try: return ranks[type(stream)] except KeyError: return len(ranks) info = asset.get_info() streams = info.get_stream_list() streams.sort(key=stream_sort_key) for stream in streams: try: beautified_string = beautify_stream(stream) except NotImplementedError: do_log(ERROR, "Beautify", "None", "Cannot beautify %s", stream) continue if beautified_string: res.append(beautified_string) duration = beautify_length(asset.get_duration()) if duration: res.append(_("<b>Duration:</b> %s") % duration) if asset.creation_progress < 100: res.append( _("<b>Proxy creation progress:</b> %d%%") % asset.creation_progress) return "\n".join(res)
def _start_thumbnailing_cb(self): if not self.__start_id: # Can happen if stopGeneration is called because the clip has been # removed from the timeline after the PreviewGeneratorManager # started this job. return False self.__start_id = None self.debug("Generating thumbnail for image: %s", path_from_uri(self.uri)) self.__image_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( Gst.uri_get_location(self.uri), -1, self.thumb_height, True) self.thumb_width = self.__image_pixbuf.props.width self._update_thumbnails() self.emit("done") # Stop calling me, I started already. return False
def beautify_asset(asset): """Formats the specified asset for display. Args: asset (GES.Asset): The asset to display. """ uri = get_proxy_target(asset).props.id path = path_from_uri(uri) res = ["<b>" + GLib.markup_escape_text(path) + "</b>"] ranks = { DiscovererVideoInfo: 0, DiscovererAudioInfo: 1, DiscovererStreamInfo: 2 } def stream_sort_key(stream): try: return ranks[type(stream)] except KeyError: return len(ranks) info = asset.get_info() streams = info.get_stream_list() streams.sort(key=stream_sort_key) for stream in streams: try: beautified_string = beautify_stream(stream) except NotImplementedError: doLog(ERROR, "Beautify", "None", "Cannot beautify %s", stream) continue if beautified_string: res.append(beautified_string) duration = beautify_length(asset.get_duration()) if duration: res.append(_("<b>Duration:</b> %s") % duration) if asset.creation_progress < 100: res.append(_("<b>Proxy creation progress:</b> %d%%") % asset.creation_progress) return "\n".join(res)
def beautify_missing_asset(asset): """Formats the specified missing asset for display. Args: asset (GES.UriClipAsset): The asset to display. """ uri = asset.get_id() path = path_from_uri(uri) res = [_("<b>Path</b>: %s") % GLib.markup_escape_text(path)] duration = beautify_length(asset.get_duration()) if duration: res.append(_("<b>Duration</b>: %s") % duration) size = asset.get_meta("file-size") if size: file_size = GLib.format_size_full(size, GLib.FormatSizeFlags.LONG_FORMAT) res.append(_("<b>Size</b>: %s") % file_size) return "\n".join(res)
def _generateThumbnails(self, uri): if not self.thumbnailer: # TODO: Use thumbnails generated with GStreamer. return None # This way of getting the mimetype feels awfully convoluted but # seems to be the proper/reliable way in a GNOME context asset_file = Gio.file_new_for_uri(uri) info = asset_file.query_info(attributes="standard::*", flags=Gio.FileQueryInfoFlags.NONE, cancellable=None) mime = Gio.content_type_get_mime_type(info.get_content_type()) mtime = os.path.getmtime(path_from_uri(uri)) if not self.thumbnailer.can_thumbnail(uri, mime, mtime): self.debug("Thumbnailer says it can't thumbnail %s", uri) return None pixbuf_128 = self.thumbnailer.generate_thumbnail(uri, mime) if not pixbuf_128: self.debug("Thumbnailer failed thumbnailing %s", uri) return None self.thumbnailer.save_thumbnail(pixbuf_128, uri, mtime) pixbuf_64 = pixbuf_128.scale_simple(64, 64, GdkPixbuf.InterpType.BILINEAR) return pixbuf_128, pixbuf_64
def _launchPipeline(self): self.debug( 'Now generating waveforms for: %s', path_from_uri(self._uri)) self.pipeline = Gst.parse_launch("uridecodebin name=decode uri=" + self._uri + " ! waveformbin name=wave" " ! fakesink qos=false name=faked") faked = self.pipeline.get_by_name("faked") faked.props.sync = True self._wavebin = self.pipeline.get_by_name("wave") asset = self.ges_elem.get_parent().get_asset() self._wavebin.props.uri = asset.get_id() self._wavebin.props.duration = asset.get_duration() decode = self.pipeline.get_by_name("decode") decode.connect("autoplug-select", self._autoplugSelectCb) bus = self.pipeline.get_bus() bus.add_signal_watch() asset = self.ges_elem.get_parent().get_asset() self.n_samples = asset.get_duration() / SAMPLE_DURATION bus.connect("message", self._busMessageCb) self.becomeControlled()
def _create_next_thumb_cb(self): """Creates a missing thumbnail.""" self._thumb_cb_id = None if not self.queue: # Nothing left to do. self.debug("Thumbnails generation complete") self.stop_generation() # Stop calling me. return False position = self.queue.pop(0) self.log("Creating thumb for `%s` at %s", path_from_uri(self.uri), position) self.pipeline.seek(1.0, Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE, Gst.SeekType.SET, position, Gst.SeekType.NONE, -1) # Stop calling me. # The seek operation will generate an ASYNC_DONE message on the bus, # and then the next thumbnail generation operation will be scheduled. return False
def _launchPipeline(self): self.debug('Now generating waveforms for: %s', path_from_uri(self._uri)) self.pipeline = Gst.parse_launch("uridecodebin name=decode uri=" + self._uri + " ! waveformbin name=wave" " ! fakesink qos=false name=faked") faked = self.pipeline.get_by_name("faked") faked.props.sync = True self._wavebin = self.pipeline.get_by_name("wave") asset = self.ges_elem.get_parent().get_asset() self._wavebin.props.uri = asset.get_id() self._wavebin.props.duration = asset.get_duration() decode = self.pipeline.get_by_name("decode") decode.connect("autoplug-select", self._autoplugSelectCb) bus = self.pipeline.get_bus() bus.add_signal_watch() asset = self.ges_elem.get_parent().get_asset() self.n_samples = asset.get_duration() / SAMPLE_DURATION bus.connect("message", self._busMessageCb) self.becomeControlled()
def beautify_asset(asset): """Formats the specified asset for display. Args: asset (GES.Asset): The asset to display. """ ranks = { DiscovererVideoInfo: 0, DiscovererAudioInfo: 1, DiscovererStreamInfo: 2 } def stream_sort_key(stream): try: return ranks[type(stream)] except KeyError: return len(ranks) info = asset.get_info() uri = get_proxy_target(asset).props.id info.get_stream_list().sort(key=stream_sort_key) nice_streams_txts = [] for stream in info.get_stream_list(): try: beautified_string = beautify_stream(stream) except NotImplementedError: doLog(ERROR, "Beautify", "None", "Cannot beautify %s", stream) continue if beautified_string: nice_streams_txts.append(beautified_string) res = "<b>" + path_from_uri(uri) + "</b>\n" + "\n".join(nice_streams_txts) if asset.creation_progress < 100: res += _("\n<b>Proxy creation progress: ") + \ "</b>%d%%" % asset.creation_progress return res
def saveProject(self, project, uri=None, overwrite=False, formatter=None, backup=False): """ Save the L{Project} to the given location. If specified, use the given formatter. @type project: L{Project} @param project: The L{Project} to save. @type uri: L{str} @param uri: The absolute URI of the location to store the project to. @param overwrite: Whether to overwrite existing location. @type overwrite: C{bool} @type formatter: L{Formatter} @param formatter: The L{Formatter} to use to store the project if specified. If it is not specified, then it will be saved at its original format. @param backup: Whether the requested save operation is for a backup @type backup: C{bool} @see: L{Formatter.saveProject} """ if formatter is None: formatter = GES.PitiviFormatter() if backup: if project.uri and self.current.uri is not None: # Ignore whatever URI that is passed on to us. It's a trap. uri = self._makeBackupURI(project.uri) else: # Do not try to save backup files for blank projects. # It is possible that self.current.uri == None when the backup # timer sent us an old instance of the (now closed) project. return elif uri is None: # This allows calling saveProject without specifying the target URI uri = project.uri else: # Ensure the URI we are given is properly encoded, or GIO will fail uri = quote_uri(uri) # The following needs to happen before we change project.uri: if not isWritable(path_from_uri(uri)): # TODO: this will not be needed when GTK+ bug #601451 is fixed self.emit("save-project-failed", uri, _("You do not have permissions to write to this folder.")) return # Update the project instance's uri for the "Save as" scenario. # Otherwise, subsequent saves will be to the old uri. if not backup: project.uri = uri if uri is None or not formatter.can_save_uri(uri): self.emit("save-project-failed", uri, _("Cannot save with this file format.")) return if overwrite or not os.path.exists(path_from_uri(uri)): formatter.set_sources(project.medialibrary.getSources()) saved = formatter.save_to_uri(project.timeline, uri) if saved: if not backup: # Do not emit the signal when autosaving a backup file self.emit("project-saved", project, uri) self.debug('Saved project "%s"' % uri) else: self.debug('Saved backup "%s"' % uri) return saved
def startLevelsDiscoveryWhenIdle(self): """Starts processing waveform (whenever possible).""" self.debug('Waiting for UI to become idle for: %s', path_from_uri(self._uri)) GLib.idle_add(self._startLevelsDiscovery, priority=GLib.PRIORITY_LOW)
def _startThumbnailingWhenIdle(self): self.debug( 'Waiting for UI to become idle for: %s', path_from_uri(self.uri)) GLib.idle_add(self._startThumbnailing, priority=GLib.PRIORITY_LOW)
def _startThumbnailingWhenIdle(self): self.debug('Waiting for UI to become idle for: %s', path_from_uri(self.uri)) GLib.idle_add(self._startThumbnailing, priority=GLib.PRIORITY_LOW)
def _addAsset(self, asset): # 128 is the normal size for thumbnails, but for *icons* it looks # insane LARGE_SIZE = 96 info = asset.get_info() # The code below tries to read existing thumbnails from the freedesktop # thumbnails directory (~/.thumbnails). The filenames are simply # the file URI hashed with md5, so we can retrieve them easily. video_streams = [ i for i in info.get_stream_list() if isinstance(i, DiscovererVideoInfo) ] if len(video_streams) > 0: # From the freedesktop spec: "if the environment variable # $XDG_CACHE_HOME is set and not blank then the directory # $XDG_CACHE_HOME/thumbnails will be used, otherwise # $HOME/.cache/thumbnails will be used." # Older version of the spec also mentioned $HOME/.thumbnails quoted_uri = quote_uri(info.get_uri()) thumbnail_hash = md5(quoted_uri.encode()).hexdigest() try: thumb_dir = os.environ['XDG_CACHE_HOME'] thumb_64, thumb_128 = self._getThumbnailInDir( thumb_dir, thumbnail_hash) except KeyError: thumb_64, thumb_128 = (None, None) if thumb_64 is None: thumb_dir = os.path.expanduser("~/.cache/thumbnails/") thumb_64, thumb_128 = self._getThumbnailInDir( thumb_dir, thumbnail_hash) if thumb_64 is None: thumb_dir = os.path.expanduser("~/.thumbnails/") thumb_64, thumb_128 = self._getThumbnailInDir( thumb_dir, thumbnail_hash) if thumb_64 is None: if asset.is_image(): thumb_64 = self._getIcon("image-x-generic") thumb_128 = self._getIcon("image-x-generic", None, LARGE_SIZE) else: thumb_64 = self._getIcon("video-x-generic") thumb_128 = self._getIcon("video-x-generic", None, LARGE_SIZE) # TODO ideally gst discoverer should create missing thumbnails. self.log("Missing a thumbnail for %s, queuing", path_from_uri(quoted_uri)) self._missing_thumbs.append(quoted_uri) else: thumb_64 = self._getIcon("audio-x-generic") thumb_128 = self._getIcon("audio-x-generic", None, LARGE_SIZE) if info.get_duration() == Gst.CLOCK_TIME_NONE: duration = '' else: duration = beautify_length(info.get_duration()) name = info_name(info) self.pending_rows.append((thumb_64, thumb_128, beautify_info(info), asset, info.get_uri(), duration, name)) if len(self.pending_rows) > 50: self.flush_pending_rows()
def start_generation(self): self.debug("Waiting for UI to become idle for: %s", path_from_uri(self.uri)) self.__start_id = GLib.idle_add(self._start_thumbnailing_cb, priority=GLib.PRIORITY_LOW)