mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-24 01:00:37 +00:00
Merging gst-devtools
This commit is contained in:
commit
86a93d746d
260 changed files with 81119 additions and 0 deletions
5
.arcconfig
Normal file
5
.arcconfig
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"phabricator.uri" : "https://phabricator.freedesktop.org/",
|
||||
"repository.callsign" : "GSTDEV",
|
||||
"project": "GStreamer Validate"
|
||||
}
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*.bak
|
||||
build*
|
||||
mesonbuild*
|
43
.gitlab-ci.yml
Normal file
43
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,43 @@
|
|||
include: "https://gitlab.freedesktop.org/gstreamer/gst-ci/raw/master/gitlab/ci_template.yml"
|
||||
|
||||
.local-rules: &local-rules
|
||||
rules:
|
||||
- changes:
|
||||
- validate/launcher/
|
||||
|
||||
# Run valgrind if we changed the check.py testsuite
|
||||
local valgrind core:
|
||||
extends: '.valgrind fedora x86_64'
|
||||
variables:
|
||||
TEST_SUITE: "check.gstreamer\\..*"
|
||||
<<: *local-rules
|
||||
|
||||
local valgrind base:
|
||||
extends: '.valgrind fedora x86_64'
|
||||
variables:
|
||||
TEST_SUITE: "check.gst-plugins-base\\..*"
|
||||
<<: *local-rules
|
||||
|
||||
local valgrind good:
|
||||
extends: '.valgrind fedora x86_64'
|
||||
variables:
|
||||
TEST_SUITE: "check.gst-plugins-good\\..*"
|
||||
<<: *local-rules
|
||||
|
||||
local valgrind ugly:
|
||||
extends: '.valgrind fedora x86_64'
|
||||
variables:
|
||||
TEST_SUITE: "check.gst-plugins-ugly\\..*"
|
||||
<<: *local-rules
|
||||
|
||||
local valgrind bad:
|
||||
extends: '.valgrind fedora x86_64'
|
||||
variables:
|
||||
TEST_SUITE: "check.gst-plugins-bad\\..*"
|
||||
<<: *local-rules
|
||||
|
||||
local valgrind ges:
|
||||
extends: '.valgrind fedora x86_64'
|
||||
variables:
|
||||
TEST_SUITE: "check.gst-editing-services\\..*"
|
||||
<<: *local-rules
|
299
NEWS
Normal file
299
NEWS
Normal file
|
@ -0,0 +1,299 @@
|
|||
GStreamer 1.20 Release Notes
|
||||
|
||||
GStreamer 1.20 has not been released yet. It is scheduled for release
|
||||
around October/November 2021.
|
||||
|
||||
1.19.x is the unstable development version that is being developed in
|
||||
the git main branch and which will eventually result in 1.20, and 1.19.2
|
||||
is the current development release in that series
|
||||
|
||||
It is expected that feature freeze will be in early October 2021,
|
||||
followed by one or two 1.19.9x pre-releases and the new 1.20 stable
|
||||
release around October/November 2021.
|
||||
|
||||
1.20 will be backwards-compatible to the stable 1.18, 1.16, 1.14, 1.12,
|
||||
1.10, 1.8, 1.6,, 1.4, 1.2 and 1.0 release series.
|
||||
|
||||
See https://gstreamer.freedesktop.org/releases/1.20/ for the latest
|
||||
version of this document.
|
||||
|
||||
Last updated: Wednesday 22 September 2021, 18:00 UTC (log)
|
||||
|
||||
Introduction
|
||||
|
||||
The GStreamer team is proud to announce a new major feature release in
|
||||
the stable 1.x API series of your favourite cross-platform multimedia
|
||||
framework!
|
||||
|
||||
As always, this release is again packed with many new features, bug
|
||||
fixes and other improvements.
|
||||
|
||||
Highlights
|
||||
|
||||
- this section will be completed in due course
|
||||
|
||||
Major new features and changes
|
||||
|
||||
Noteworthy new features and API
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
New elements
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
New element features and additions
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Plugin and library moves
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
- There were no plugin moves or library moves in this cycle.
|
||||
|
||||
Plugin removals
|
||||
|
||||
The following elements or plugins have been removed:
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Miscellaneous API additions
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Miscellaneous performance, latency and memory optimisations
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Miscellaneous other changes and enhancements
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Tracing framework and debugging improvements
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Tools
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer RTSP server
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer VAAPI
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer OMX
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer Editing Services and NLE
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer validate
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer Python Bindings
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer C# Bindings
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
GStreamer Rust Bindings and Rust Plugins
|
||||
|
||||
The GStreamer Rust bindings are released separately with a different
|
||||
release cadence that’s tied to gtk-rs, but the latest release has
|
||||
already been updated for the upcoming new GStreamer 1.20 API.
|
||||
|
||||
gst-plugins-rs, the module containing GStreamer plugins written in Rust,
|
||||
has also seen lots of activity with many new elements and plugins.
|
||||
|
||||
What follows is a list of elements and plugins available in
|
||||
gst-plugins-rs, so people don’t miss out on all those potentially useful
|
||||
elements that have no C equivalent.
|
||||
|
||||
- FIXME: add new elements
|
||||
|
||||
Rust audio plugins
|
||||
|
||||
- audiornnoise: New element for audio denoising which implements the
|
||||
noise removal algorithm of the Xiph RNNoise library, in Rust
|
||||
- rsaudioecho: Port of the audioecho element from gst-plugins-good
|
||||
rsaudioloudnorm: Live audio loudness normalization element based on
|
||||
the FFmpeg af_loudnorm filter
|
||||
- claxondec: FLAC lossless audio codec decoder element based on the
|
||||
pure-Rust claxon implementation
|
||||
- csoundfilter: Audio filter that can use any filter defined via the
|
||||
Csound audio programming language
|
||||
- lewtondec: Vorbis audio decoder element based on the pure-Rust
|
||||
lewton implementation
|
||||
|
||||
Rust video plugins
|
||||
|
||||
- cdgdec/cdgparse: Decoder and parser for the CD+G video codec based
|
||||
on a pure-Rust CD+G implementation, used for example by karaoke CDs
|
||||
- cea608overlay: CEA-608 Closed Captions overlay element
|
||||
- cea608tott: CEA-608 Closed Captions to timed-text (e.g. VTT or SRT
|
||||
subtitles) converter
|
||||
- tttocea608: CEA-608 Closed Captions from timed-text converter
|
||||
- mccenc/mccparse: MacCaption Closed Caption format encoder and parser
|
||||
- sccenc/sccparse: Scenarist Closed Caption format encoder and parser
|
||||
- dav1dec: AV1 video decoder based on the dav1d decoder implementation
|
||||
by the VLC project
|
||||
- rav1enc: AV1 video encoder based on the fast and pure-Rust rav1e
|
||||
encoder implementation
|
||||
- rsflvdemux: Alternative to the flvdemux FLV demuxer element from
|
||||
gst-plugins-good, not feature-equivalent yet
|
||||
- rsgifenc/rspngenc: GIF/PNG encoder elements based on the pure-Rust
|
||||
implementations by the image-rs project
|
||||
|
||||
Rust text plugins
|
||||
|
||||
- textwrap: Element for line-wrapping timed text (e.g. subtitles) for
|
||||
better screen-fitting, including hyphenation support for some
|
||||
languages
|
||||
|
||||
Rust network plugins
|
||||
|
||||
- reqwesthttpsrc: HTTP(S) source element based on the Rust
|
||||
reqwest/hyper HTTP implementations and almost feature-equivalent
|
||||
with the main GStreamer HTTP source souphttpsrc
|
||||
- s3src/s3sink: Source/sink element for the Amazon S3 cloud storage
|
||||
- awstranscriber: Live audio to timed text transcription element using
|
||||
the Amazon AWS Transcribe API
|
||||
|
||||
Generic Rust plugins
|
||||
|
||||
- sodiumencrypter/sodiumdecrypter: Encryption/decryption element based
|
||||
on libsodium/NaCl
|
||||
- togglerecord: Recording element that allows to pause/resume
|
||||
recordings easily and considers keyframe boundaries
|
||||
- fallbackswitch/fallbacksrc: Elements for handling potentially
|
||||
failing (network) sources, restarting them on errors/timeout and
|
||||
showing a fallback stream instead
|
||||
- threadshare: Set of elements that provide alternatives for various
|
||||
existing GStreamer elements but allow to share the streaming threads
|
||||
between each other to reduce the number of threads
|
||||
- rsfilesrc/rsfilesink: File source/sink elements as replacements for
|
||||
the existing filesrc/filesink elements
|
||||
|
||||
Build and Dependencies
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
gst-build
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Cerbero
|
||||
|
||||
Cerbero is a meta build system used to build GStreamer plus dependencies
|
||||
on platforms where dependencies are not readily available, such as
|
||||
Windows, Android, iOS and macOS.
|
||||
|
||||
General improvements
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
macOS / iOS
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Windows
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Windows MSI installer
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Linux
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Android
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Platform-specific changes and improvements
|
||||
|
||||
Android
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
macOS and iOS
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Windows
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Linux
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Documentation improvements
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
Possibly Breaking Changes
|
||||
|
||||
- this section will be filled in in due course
|
||||
- MPEG-TS SCTE-35 API changes (FIXME: flesh out)
|
||||
- gst_parse_launch() and friends now error out on non-existing
|
||||
properties on top-level bins where they would silently fail and
|
||||
ignore those before.
|
||||
|
||||
Known Issues
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
- There are a couple of known WebRTC-related regressions/blockers:
|
||||
|
||||
- webrtc: DTLS setup with Chrome is broken
|
||||
- webrtcbin: First keyframe is usually lost
|
||||
|
||||
Contributors
|
||||
|
||||
- this section will be filled in in due course
|
||||
|
||||
… and many others who have contributed bug reports, translations, sent
|
||||
suggestions or helped testing.
|
||||
|
||||
Stable 1.20 branch
|
||||
|
||||
After the 1.20.0 release there will be several 1.20.x bug-fix releases
|
||||
which will contain bug fixes which have been deemed suitable for a
|
||||
stable branch, but no new features or intrusive changes will be added to
|
||||
a bug-fix release usually. The 1.20.x bug-fix releases will be made from
|
||||
the git 1.20 branch, which will be a stable branch.
|
||||
|
||||
1.20.0
|
||||
|
||||
1.20.0 is scheduled to be released around October/November 2021.
|
||||
|
||||
Schedule for 1.22
|
||||
|
||||
Our next major feature release will be 1.22, and 1.21 will be the
|
||||
unstable development version leading up to the stable 1.22 release. The
|
||||
development of 1.21/1.22 will happen in the git main branch.
|
||||
|
||||
The plan for the 1.22 development cycle is yet to be confirmed.
|
||||
|
||||
1.22 will be backwards-compatible to the stable 1.20, 1.18, 1.16, 1.14,
|
||||
1.12, 1.10, 1.8, 1.6, 1.4, 1.2 and 1.0 release series.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
These release notes have been prepared by Tim-Philipp Müller with
|
||||
contributions from …
|
||||
|
||||
License: CC BY-SA 4.0
|
96
RELEASE
Normal file
96
RELEASE
Normal file
|
@ -0,0 +1,96 @@
|
|||
This is GStreamer gst-devtools 1.19.2.
|
||||
|
||||
GStreamer 1.19 is the development branch leading up to the next major
|
||||
stable version which will be 1.20.
|
||||
|
||||
The 1.19 development series adds new features on top of the 1.18 series and is
|
||||
part of the API and ABI-stable 1.x release series of the GStreamer multimedia
|
||||
framework.
|
||||
|
||||
Full release notes will one day be found at:
|
||||
|
||||
https://gstreamer.freedesktop.org/releases/1.20/
|
||||
|
||||
Binaries for Android, iOS, Mac OS X and Windows will usually be provided
|
||||
shortly after the release.
|
||||
|
||||
This module will not be very useful by itself and should be used in conjunction
|
||||
with other GStreamer modules for a complete multimedia experience.
|
||||
|
||||
- gstreamer: provides the core GStreamer libraries and some generic plugins
|
||||
|
||||
- gst-plugins-base: a basic set of well-supported plugins and additional
|
||||
media-specific GStreamer helper libraries for audio,
|
||||
video, rtsp, rtp, tags, OpenGL, etc.
|
||||
|
||||
- gst-plugins-good: a set of well-supported plugins under our preferred
|
||||
license
|
||||
|
||||
- gst-plugins-ugly: a set of well-supported plugins which might pose
|
||||
problems for distributors
|
||||
|
||||
- gst-plugins-bad: a set of plugins of varying quality that have not made
|
||||
their way into one of core/base/good/ugly yet, for one
|
||||
reason or another. Many of these are are production quality
|
||||
elements, but may still be missing documentation or unit
|
||||
tests; others haven't passed the rigorous quality testing
|
||||
we expect yet.
|
||||
|
||||
- gst-libav: a set of codecs plugins based on the ffmpeg library. This is
|
||||
where you can find audio and video decoders and encoders
|
||||
for a wide variety of formats including H.264, AAC, etc.
|
||||
|
||||
- gstreamer-vaapi: hardware-accelerated video decoding and encoding using
|
||||
VA-API on Linux. Primarily for Intel graphics hardware.
|
||||
|
||||
- gst-omx: hardware-accelerated video decoding and encoding, primarily for
|
||||
embedded Linux systems that provide an OpenMax
|
||||
implementation layer such as the Raspberry Pi.
|
||||
|
||||
- gst-rtsp-server: library to serve files or streaming pipelines via RTSP
|
||||
|
||||
- gst-editing-services: library an plugins for non-linear editing
|
||||
|
||||
==== Download ====
|
||||
|
||||
You can find source releases of gstreamer in the download
|
||||
directory: https://gstreamer.freedesktop.org/src/gstreamer/
|
||||
|
||||
The git repository and details how to clone it can be found at
|
||||
https://gitlab.freedesktop.org/gstreamer/
|
||||
|
||||
==== Homepage ====
|
||||
|
||||
The project's website is https://gstreamer.freedesktop.org/
|
||||
|
||||
==== Support and Bugs ====
|
||||
|
||||
We have recently moved from GNOME Bugzilla to GitLab on freedesktop.org
|
||||
for bug reports and feature requests:
|
||||
|
||||
https://gitlab.freedesktop.org/gstreamer
|
||||
|
||||
Please submit patches via GitLab as well, in form of Merge Requests. See
|
||||
|
||||
https://gstreamer.freedesktop.org/documentation/contribute/
|
||||
|
||||
for more details.
|
||||
|
||||
For help and support, please subscribe to and send questions to the
|
||||
gstreamer-devel mailing list (see below for details).
|
||||
|
||||
There is also a #gstreamer IRC channel on the Freenode IRC network.
|
||||
|
||||
==== Developers ====
|
||||
|
||||
GStreamer source code repositories can be found on GitLab on freedesktop.org:
|
||||
|
||||
https://gitlab.freedesktop.org/gstreamer
|
||||
|
||||
and can also be cloned from there and this is also where you can submit
|
||||
Merge Requests or file issues for bugs or feature requests.
|
||||
|
||||
Interested developers of the core library, plugins, and applications should
|
||||
subscribe to the gstreamer-devel list:
|
||||
|
||||
https://lists.freedesktop.org/mailman/listinfo/gstreamer-devel
|
14
debug-viewer/.gitignore
vendored
Normal file
14
debug-viewer/.gitignore
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
*.glade.bak
|
||||
*.gladep
|
||||
*.gladep.bak
|
||||
|
||||
/build
|
||||
/dist
|
||||
/MANIFEST
|
||||
|
||||
po/*.pot
|
||||
po/mo
|
74
debug-viewer/GstDebugViewer/Common/Data.py
Normal file
74
debug-viewer/GstDebugViewer/Common/Data.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Development Utilities
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Development Utilities Common Data module."""
|
||||
|
||||
import gi
|
||||
|
||||
from gi.repository import GObject
|
||||
|
||||
|
||||
class Dispatcher (object):
|
||||
|
||||
def __call__(self, iterator):
|
||||
|
||||
raise NotImplementedError("derived classes must override this method")
|
||||
|
||||
def cancel(self):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DefaultDispatcher (Dispatcher):
|
||||
|
||||
def __call__(self, iterator):
|
||||
|
||||
for x in iterator:
|
||||
pass
|
||||
|
||||
|
||||
class GSourceDispatcher (Dispatcher):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
Dispatcher.__init__(self)
|
||||
|
||||
self.source_id = None
|
||||
|
||||
def __call__(self, iterator):
|
||||
|
||||
if self.source_id is not None:
|
||||
GObject.source_remove(self.source_id)
|
||||
|
||||
def iteration():
|
||||
r = iterator.__next__()
|
||||
if not r:
|
||||
self.source_id = None
|
||||
return r
|
||||
|
||||
self.source_id = GObject.idle_add(
|
||||
iteration, priority=GObject.PRIORITY_LOW)
|
||||
|
||||
def cancel(self):
|
||||
|
||||
if self.source_id is None:
|
||||
return
|
||||
|
||||
GObject.source_remove(self.source_id)
|
||||
self.source_id = None
|
528
debug-viewer/GstDebugViewer/Common/GUI.py
Normal file
528
debug-viewer/GstDebugViewer/Common/GUI.py
Normal file
|
@ -0,0 +1,528 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Development Utilities
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Development Utilities Common GUI module."""
|
||||
|
||||
import os
|
||||
|
||||
import logging
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.types import GObjectMeta
|
||||
|
||||
import GstDebugViewer
|
||||
from GstDebugViewer.Common import utils
|
||||
from .generictreemodel import GenericTreeModel
|
||||
|
||||
|
||||
def widget_add_popup_menu(widget, menu, button=3):
|
||||
|
||||
def popup_callback(widget, event):
|
||||
|
||||
if event.button == button:
|
||||
menu.popup(
|
||||
None, None, None, None, event.button, event.get_time())
|
||||
return False
|
||||
|
||||
widget.connect("button-press-event", popup_callback)
|
||||
|
||||
|
||||
class Actions (dict):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
dict.__init__(self)
|
||||
|
||||
self.groups = {}
|
||||
|
||||
def __getattr__(self, name):
|
||||
|
||||
try:
|
||||
return self[name]
|
||||
except KeyError:
|
||||
if "_" in name:
|
||||
try:
|
||||
return self[name.replace("_", "-")]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
raise AttributeError("no action with name %r" % (name,))
|
||||
|
||||
def add_group(self, group):
|
||||
|
||||
name = group.props.name
|
||||
if name in self.groups:
|
||||
raise ValueError("already have a group named %s", name)
|
||||
self.groups[name] = group
|
||||
for action in group.list_actions():
|
||||
self[action.props.name] = action
|
||||
|
||||
|
||||
class Widgets (dict):
|
||||
|
||||
def __init__(self, builder):
|
||||
|
||||
widgets = (obj for obj in builder.get_objects()
|
||||
if isinstance(obj, Gtk.Buildable))
|
||||
# Gtk.Widget.get_name() shadows out the GtkBuildable interface method
|
||||
# of the same name, hence calling the unbound interface method here:
|
||||
items = ((Gtk.Buildable.get_name(w), w,) for w in widgets)
|
||||
|
||||
dict.__init__(self, items)
|
||||
|
||||
def __getattr__(self, name):
|
||||
|
||||
try:
|
||||
return self[name]
|
||||
except KeyError:
|
||||
if "_" in name:
|
||||
try:
|
||||
return self[name.replace("_", "-")]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
raise AttributeError("no widget with name %r" % (name,))
|
||||
|
||||
|
||||
class WidgetFactory (object):
|
||||
|
||||
def __init__(self, directory):
|
||||
|
||||
self.directory = directory
|
||||
|
||||
def get_builder(self, filename):
|
||||
|
||||
builder_filename = os.path.join(self.directory, filename)
|
||||
|
||||
builder = Gtk.Builder()
|
||||
builder.set_translation_domain(GstDebugViewer.GETTEXT_DOMAIN)
|
||||
builder.add_from_file(builder_filename)
|
||||
|
||||
return builder
|
||||
|
||||
def make(self, filename, widget_name, autoconnect=None):
|
||||
|
||||
builder = self.get_builder(filename)
|
||||
|
||||
if autoconnect is not None:
|
||||
builder.connect_signals(autoconnect)
|
||||
|
||||
return Widgets(builder)
|
||||
|
||||
def make_one(self, filename, widget_name):
|
||||
|
||||
builder = self.get_builder(filename)
|
||||
|
||||
return builder.get_object(widget_name)
|
||||
|
||||
|
||||
class UIFactory (object):
|
||||
|
||||
def __init__(self, ui_filename, actions=None):
|
||||
|
||||
self.filename = ui_filename
|
||||
if actions:
|
||||
self.action_groups = actions.groups
|
||||
else:
|
||||
self.action_groups = ()
|
||||
|
||||
def make(self, extra_actions=None):
|
||||
|
||||
ui_manager = Gtk.UIManager()
|
||||
for action_group in list(self.action_groups.values()):
|
||||
ui_manager.insert_action_group(action_group, 0)
|
||||
if extra_actions:
|
||||
for action_group in extra_actions.groups:
|
||||
ui_manager.insert_action_group(action_group, 0)
|
||||
ui_manager.add_ui_from_file(self.filename)
|
||||
ui_manager.ensure_update()
|
||||
|
||||
return ui_manager
|
||||
|
||||
|
||||
class MetaModel (GObjectMeta):
|
||||
|
||||
"""Meta class for easy setup of gtk tree models.
|
||||
|
||||
Looks for a class attribute named `columns' which must be set to a
|
||||
sequence of the form name1, type1, name2, type2, ..., where the
|
||||
names are strings. This metaclass adds the following attributes
|
||||
to created classes:
|
||||
|
||||
cls.column_types = (type1, type2, ...)
|
||||
cls.column_ids = (0, 1, ...)
|
||||
cls.name1 = 0
|
||||
cls.name2 = 1
|
||||
...
|
||||
|
||||
Example: A Gtk.ListStore derived model can use
|
||||
|
||||
columns = ("COL_NAME", str, "COL_VALUE", str)
|
||||
|
||||
and use this in __init__:
|
||||
|
||||
GObject.GObject.__init__ (self, *self.column_types)
|
||||
|
||||
Then insert data like this:
|
||||
|
||||
self.set (self.append (),
|
||||
self.COL_NAME, "spam",
|
||||
self.COL_VALUE, "ham")
|
||||
"""
|
||||
|
||||
def __init__(cls, name, bases, dict):
|
||||
|
||||
super(MetaModel, cls).__init__(name, bases, dict)
|
||||
|
||||
spec = tuple(cls.columns)
|
||||
|
||||
column_names = spec[::2]
|
||||
column_types = spec[1::2]
|
||||
column_indices = list(range(len(column_names)))
|
||||
|
||||
for col_index, col_name, in zip(column_indices, column_names):
|
||||
setattr(cls, col_name, col_index)
|
||||
|
||||
cls.column_types = column_types
|
||||
cls.column_ids = tuple(column_indices)
|
||||
|
||||
|
||||
class Manager (object):
|
||||
|
||||
"""GUI Manager base class."""
|
||||
|
||||
@classmethod
|
||||
def iter_item_classes(cls):
|
||||
|
||||
msg = "%s class does not support manager item class access"
|
||||
raise NotImplementedError(msg % (cls.__name__,))
|
||||
|
||||
@classmethod
|
||||
def find_item_class(self, **kw):
|
||||
|
||||
return self.__find_by_attrs(self.iter_item_classes(), kw)
|
||||
|
||||
def iter_items(self):
|
||||
|
||||
msg = "%s object does not support manager item access"
|
||||
raise NotImplementedError(msg % (type(self).__name__,))
|
||||
|
||||
def find_item(self, **kw):
|
||||
|
||||
return self.__find_by_attrs(self.iter_items(), kw)
|
||||
|
||||
@staticmethod
|
||||
def __find_by_attrs(i, kw):
|
||||
|
||||
from operator import attrgetter
|
||||
|
||||
if len(kw) != 1:
|
||||
raise ValueError("need exactly one keyword argument")
|
||||
|
||||
attr, value = list(kw.items())[0]
|
||||
getter = attrgetter(attr)
|
||||
|
||||
for item in i:
|
||||
if getter(item) == value:
|
||||
return item
|
||||
else:
|
||||
raise KeyError("no item such that item.%s == %r" % (attr, value,))
|
||||
|
||||
|
||||
class StateString (object):
|
||||
|
||||
"""Descriptor for binding to StateSection classes."""
|
||||
|
||||
def __init__(self, option, default=None):
|
||||
|
||||
self.option = option
|
||||
self.default = default
|
||||
|
||||
def __get__(self, section, section_class=None):
|
||||
|
||||
import configparser
|
||||
|
||||
if section is None:
|
||||
return self
|
||||
|
||||
try:
|
||||
return self.get(section)
|
||||
except (configparser.NoSectionError,
|
||||
configparser.NoOptionError,):
|
||||
return self.get_default(section)
|
||||
|
||||
def __set__(self, section, value):
|
||||
|
||||
import configparser
|
||||
|
||||
self.set(section, value)
|
||||
|
||||
def get(self, section):
|
||||
|
||||
return section.get(self)
|
||||
|
||||
def get_default(self, section):
|
||||
|
||||
return self.default
|
||||
|
||||
def set(self, section, value):
|
||||
|
||||
if value is None:
|
||||
value = ""
|
||||
|
||||
section.set(self, str(value))
|
||||
|
||||
|
||||
class StateBool (StateString):
|
||||
|
||||
"""Descriptor for binding to StateSection classes."""
|
||||
|
||||
def get(self, section):
|
||||
|
||||
return section.state._parser.getboolean(section._name, self.option)
|
||||
|
||||
|
||||
class StateInt (StateString):
|
||||
|
||||
"""Descriptor for binding to StateSection classes."""
|
||||
|
||||
def get(self, section):
|
||||
|
||||
return section.state._parser.getint(section._name, self.option)
|
||||
|
||||
|
||||
class StateInt4 (StateString):
|
||||
|
||||
"""Descriptor for binding to StateSection classes. This implements storing
|
||||
a tuple of 4 integers."""
|
||||
|
||||
def get(self, section):
|
||||
|
||||
value = StateString.get(self, section)
|
||||
|
||||
try:
|
||||
l = value.split(",")
|
||||
if len(l) != 4:
|
||||
return None
|
||||
else:
|
||||
return tuple((int(v) for v in l))
|
||||
except (AttributeError, TypeError, ValueError,):
|
||||
return None
|
||||
|
||||
def set(self, section, value):
|
||||
|
||||
if value is None:
|
||||
svalue = ""
|
||||
elif len(value) != 4:
|
||||
raise ValueError("value needs to be a 4-sequence, or None")
|
||||
else:
|
||||
svalue = ", ".join((str(v) for v in value))
|
||||
|
||||
return StateString.set(self, section, svalue)
|
||||
|
||||
|
||||
class StateItem (StateString):
|
||||
|
||||
"""Descriptor for binding to StateSection classes. This implements storing
|
||||
a class controlled by a Manager class."""
|
||||
|
||||
def __init__(self, option, manager_class, default=None):
|
||||
|
||||
StateString.__init__(self, option, default=default)
|
||||
|
||||
self.manager = manager_class
|
||||
|
||||
def get(self, section):
|
||||
|
||||
value = SectionString.get(self, section)
|
||||
|
||||
if not value:
|
||||
return None
|
||||
|
||||
return self.parse_item(value)
|
||||
|
||||
def set(self, section, value):
|
||||
|
||||
if value is None:
|
||||
svalue = ""
|
||||
else:
|
||||
svalue = value.name
|
||||
|
||||
StateString.set(self, section, svalue)
|
||||
|
||||
def parse_item(self, value):
|
||||
|
||||
name = value.strip()
|
||||
|
||||
try:
|
||||
return self.manager.find_item_class(name=name)
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
class StateItemList (StateItem):
|
||||
|
||||
"""Descriptor for binding to StateSection classes. This implements storing
|
||||
an ordered set of Manager items."""
|
||||
|
||||
def get(self, section):
|
||||
|
||||
value = StateString.get(self, section)
|
||||
|
||||
if not value:
|
||||
return []
|
||||
|
||||
classes = []
|
||||
for name in value.split(","):
|
||||
item_class = self.parse_item(name)
|
||||
if item_class is None:
|
||||
continue
|
||||
if not item_class in classes:
|
||||
classes.append(item_class)
|
||||
|
||||
return classes
|
||||
|
||||
def get_default(self, section):
|
||||
|
||||
default = StateItem.get_default(self, section)
|
||||
if default is None:
|
||||
return []
|
||||
else:
|
||||
return default
|
||||
|
||||
def set(self, section, value):
|
||||
|
||||
if value is None:
|
||||
svalue = ""
|
||||
else:
|
||||
svalue = ", ".join((v.name for v in value))
|
||||
|
||||
StateString.set(self, section, svalue)
|
||||
|
||||
|
||||
class StateSection (object):
|
||||
|
||||
_name = None
|
||||
|
||||
def __init__(self, state):
|
||||
|
||||
self.state = state
|
||||
|
||||
if self._name is None:
|
||||
raise NotImplementedError(
|
||||
"subclasses must override the _name attribute")
|
||||
|
||||
def get(self, state_string):
|
||||
|
||||
return self.state._parser.get(self._name, state_string.option)
|
||||
|
||||
def set(self, state_string, value):
|
||||
|
||||
import configparser
|
||||
|
||||
parser = self.state._parser
|
||||
|
||||
try:
|
||||
parser.set(self._name, state_string.option, value)
|
||||
except configparser.NoSectionError:
|
||||
parser.add_section(self._name)
|
||||
parser.set(self._name, state_string.option, value)
|
||||
|
||||
|
||||
class State (object):
|
||||
|
||||
def __init__(self, filename, old_filenames=()):
|
||||
|
||||
import configparser
|
||||
|
||||
self.sections = {}
|
||||
|
||||
self._filename = filename
|
||||
self._parser = configparser.RawConfigParser()
|
||||
success = self._parser.read([filename])
|
||||
if not success:
|
||||
for old_filename in old_filenames:
|
||||
success = self._parser.read([old_filename])
|
||||
if success:
|
||||
break
|
||||
|
||||
def add_section_class(self, section_class):
|
||||
|
||||
self.sections[section_class._name] = section_class(self)
|
||||
|
||||
def save(self):
|
||||
|
||||
with utils.SaveWriteFile(self._filename, "wt") as fp:
|
||||
self._parser.write(fp)
|
||||
|
||||
|
||||
class WindowState (object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.logger = logging.getLogger("ui.window-state")
|
||||
|
||||
self.is_maximized = False
|
||||
|
||||
def attach(self, window, state):
|
||||
|
||||
self.window = window
|
||||
self.state = state
|
||||
|
||||
self.window.connect("window-state-event",
|
||||
self.handle_window_state_event)
|
||||
|
||||
geometry = self.state.geometry
|
||||
if geometry:
|
||||
self.window.move(*geometry[:2])
|
||||
self.window.set_default_size(*geometry[2:])
|
||||
|
||||
if self.state.maximized:
|
||||
self.logger.debug("initially maximized")
|
||||
self.window.maximize()
|
||||
|
||||
def detach(self):
|
||||
|
||||
window = self.window
|
||||
|
||||
self.state.maximized = self.is_maximized
|
||||
if not self.is_maximized:
|
||||
position = tuple(window.get_position())
|
||||
size = tuple(window.get_size())
|
||||
self.state.geometry = position + size
|
||||
|
||||
self.window.disconnect_by_func(self.handle_window_state_event)
|
||||
self.window = None
|
||||
|
||||
def handle_window_state_event(self, window, event):
|
||||
|
||||
if not event.changed_mask & Gdk.WindowState.MAXIMIZED:
|
||||
return
|
||||
|
||||
if event.new_window_state & Gdk.WindowState.MAXIMIZED:
|
||||
self.logger.debug("maximized")
|
||||
self.is_maximized = True
|
||||
else:
|
||||
self.logger.debug("unmaximized")
|
||||
self.is_maximized = False
|
366
debug-viewer/GstDebugViewer/Common/Main.py
Normal file
366
debug-viewer/GstDebugViewer/Common/Main.py
Normal file
|
@ -0,0 +1,366 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Development Utilities
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Development Utilities Common Main module."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
from operator import attrgetter
|
||||
import logging
|
||||
import locale
|
||||
import gettext
|
||||
from gettext import gettext as _, ngettext
|
||||
|
||||
import gi
|
||||
|
||||
from gi.repository import GLib
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class ExceptionHandler (object):
|
||||
|
||||
exc_types = (Exception,)
|
||||
priority = 50
|
||||
inherit_fork = True
|
||||
|
||||
_handling_exception = False
|
||||
|
||||
def __call__(self, exc_type, exc_value, exc_traceback):
|
||||
|
||||
raise NotImplementedError(
|
||||
"derived classes need to override this method")
|
||||
|
||||
|
||||
class DefaultExceptionHandler (ExceptionHandler):
|
||||
exc_types = (BaseException,)
|
||||
priority = 0
|
||||
inherit_fork = True
|
||||
|
||||
def __init__(self, excepthook):
|
||||
|
||||
ExceptionHandler.__init__(self)
|
||||
|
||||
self.excepthook = excepthook
|
||||
|
||||
def __call__(self, *exc_info):
|
||||
|
||||
return self.excepthook(*exc_info)
|
||||
|
||||
|
||||
class ExitOnInterruptExceptionHandler (ExceptionHandler):
|
||||
|
||||
exc_types = (KeyboardInterrupt,)
|
||||
priority = 100
|
||||
inherit_fork = False
|
||||
|
||||
exit_status = 2
|
||||
|
||||
def __call__(self, *args):
|
||||
|
||||
print("Interrupt caught, exiting.", file=sys.stderr)
|
||||
|
||||
sys.exit(self.exit_status)
|
||||
|
||||
|
||||
class MainLoopWrapper (ExceptionHandler):
|
||||
|
||||
priority = 95
|
||||
inherit_fork = False
|
||||
|
||||
def __init__(self, enter, exit):
|
||||
|
||||
ExceptionHandler.__init__(self)
|
||||
|
||||
self.exc_info = (None,) * 3
|
||||
self.enter = enter
|
||||
self.exit = exit
|
||||
|
||||
def __call__(self, *exc_info):
|
||||
|
||||
self.exc_info = exc_info
|
||||
self.exit()
|
||||
|
||||
def run(self):
|
||||
|
||||
ExceptHookManager.register_handler(self)
|
||||
try:
|
||||
self.enter()
|
||||
finally:
|
||||
ExceptHookManager.unregister_handler(self)
|
||||
|
||||
if self.exc_info != (None,) * 3:
|
||||
# Re-raise unhandled exception that occured while running the loop.
|
||||
exc_type, exc_value, exc_tb = self.exc_info
|
||||
raise exc_value
|
||||
|
||||
|
||||
class ExceptHookManagerClass (object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self._in_forked_child = False
|
||||
|
||||
self.handlers = []
|
||||
|
||||
def setup(self):
|
||||
|
||||
if sys.excepthook == self.__excepthook:
|
||||
raise ValueError("already set up")
|
||||
|
||||
hook = sys.excepthook
|
||||
self.__instrument_excepthook()
|
||||
self.__instrument_fork()
|
||||
self.register_handler(DefaultExceptionHandler(hook))
|
||||
|
||||
def shutdown(self):
|
||||
|
||||
if sys.excepthook != self.__excepthook:
|
||||
raise ValueError("not set up")
|
||||
|
||||
self.__restore_excepthook()
|
||||
self.__restore_fork()
|
||||
|
||||
def __instrument_excepthook(self):
|
||||
|
||||
hook = sys.excepthook
|
||||
self._original_excepthook = hook
|
||||
sys.excepthook = self.__excepthook
|
||||
|
||||
def __restore_excepthook(self):
|
||||
|
||||
sys.excepthook = self._original_excepthook
|
||||
|
||||
def __instrument_fork(self):
|
||||
|
||||
try:
|
||||
fork = os.fork
|
||||
except AttributeError:
|
||||
# System has no fork() system call.
|
||||
self._original_fork = None
|
||||
else:
|
||||
self._original_fork = fork
|
||||
os.fork = self.__fork
|
||||
|
||||
def __restore_fork(self):
|
||||
|
||||
if not hasattr(os, "fork"):
|
||||
return
|
||||
|
||||
os.fork = self._original_fork
|
||||
|
||||
def entered_forked_child(self):
|
||||
|
||||
self._in_forked_child = True
|
||||
|
||||
for handler in tuple(self.handlers):
|
||||
if not handler.inherit_fork:
|
||||
self.handlers.remove(handler)
|
||||
|
||||
def register_handler(self, handler):
|
||||
|
||||
if self._in_forked_child and not handler.inherit_fork:
|
||||
return
|
||||
|
||||
self.handlers.append(handler)
|
||||
|
||||
def unregister_handler(self, handler):
|
||||
|
||||
self.handlers.remove(handler)
|
||||
|
||||
def __fork(self):
|
||||
|
||||
pid = self._original_fork()
|
||||
if pid == 0:
|
||||
# Child process.
|
||||
self.entered_forked_child()
|
||||
return pid
|
||||
|
||||
def __excepthook(self, exc_type, exc_value, exc_traceback):
|
||||
|
||||
for handler in sorted(self.handlers,
|
||||
key=attrgetter("priority"),
|
||||
reverse=True):
|
||||
|
||||
if handler._handling_exception:
|
||||
continue
|
||||
|
||||
for type_ in handler.exc_types:
|
||||
if issubclass(exc_type, type_):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
|
||||
handler._handling_exception = True
|
||||
handler(exc_type, exc_value, exc_traceback)
|
||||
# Not using try...finally on purpose here. If the handler itself
|
||||
# fails with an exception, this prevents recursing into it again.
|
||||
handler._handling_exception = False
|
||||
return
|
||||
|
||||
else:
|
||||
from warnings import warn
|
||||
warn("ExceptHookManager: unhandled %r" % (exc_value,),
|
||||
RuntimeWarning,
|
||||
stacklevel=2)
|
||||
|
||||
|
||||
ExceptHookManager = ExceptHookManagerClass()
|
||||
|
||||
|
||||
class PathsBase (object):
|
||||
|
||||
data_dir = None
|
||||
icon_dir = None
|
||||
locale_dir = None
|
||||
|
||||
@classmethod
|
||||
def setup_installed(cls, data_prefix):
|
||||
"""Set up paths for running from a regular installation."""
|
||||
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def setup_uninstalled(cls, source_dir):
|
||||
"""Set up paths for running 'uninstalled' (i.e. directly from the
|
||||
source dist)."""
|
||||
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def ensure_setup(cls):
|
||||
"""If paths are still not set up, try to set from a fallback."""
|
||||
|
||||
if cls.data_dir is None:
|
||||
source_dir = os.path.dirname(
|
||||
os.path.dirname(os.path.abspath(__file__)))
|
||||
cls.setup_uninstalled(source_dir)
|
||||
|
||||
def __new__(cls):
|
||||
|
||||
raise RuntimeError("do not create instances of this class -- "
|
||||
"use the class object directly")
|
||||
|
||||
|
||||
class PathsProgramBase (PathsBase):
|
||||
|
||||
program_name = None
|
||||
|
||||
@classmethod
|
||||
def setup_installed(cls, data_prefix):
|
||||
|
||||
if cls.program_name is None:
|
||||
raise NotImplementedError(
|
||||
"derived classes need to set program_name attribute")
|
||||
|
||||
cls.data_dir = os.path.join(data_prefix, cls.program_name)
|
||||
cls.icon_dir = os.path.join(data_prefix, "icons")
|
||||
cls.locale_dir = os.path.join(data_prefix, "locale")
|
||||
|
||||
@classmethod
|
||||
def setup_uninstalled(cls, source_dir):
|
||||
"""Set up paths for running 'uninstalled' (i.e. directly from the
|
||||
source dist)."""
|
||||
|
||||
# This is essential: The GUI module needs to find the .glade file.
|
||||
cls.data_dir = os.path.join(source_dir, "data")
|
||||
|
||||
# The locale data might be missing if "setup.py build" wasn't run.
|
||||
cls.locale_dir = os.path.join(source_dir, "build", "mo")
|
||||
|
||||
# Not setting icon_dir. It is not useful since we don't employ the
|
||||
# needed directory structure in the source dist.
|
||||
|
||||
|
||||
def _init_excepthooks():
|
||||
|
||||
ExceptHookManager.setup()
|
||||
ExceptHookManager.register_handler(ExitOnInterruptExceptionHandler())
|
||||
|
||||
|
||||
def _init_paths(paths):
|
||||
|
||||
paths.ensure_setup()
|
||||
|
||||
|
||||
def _init_locale(gettext_domain=None):
|
||||
|
||||
if Paths.locale_dir and gettext_domain is not None:
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
except locale.Error as exc:
|
||||
from warnings import warn
|
||||
warn("locale error: %s" % (exc,),
|
||||
RuntimeWarning,
|
||||
stacklevel=2)
|
||||
Paths.locale_dir = None
|
||||
else:
|
||||
gettext.bindtextdomain(gettext_domain, Paths.locale_dir)
|
||||
gettext.textdomain(gettext_domain)
|
||||
gettext.bind_textdomain_codeset(gettext_domain, "UTF-8")
|
||||
|
||||
|
||||
def _init_logging(level):
|
||||
if level == "none":
|
||||
return
|
||||
|
||||
mapping = {"debug": logging.DEBUG,
|
||||
"info": logging.INFO,
|
||||
"warning": logging.WARNING,
|
||||
"error": logging.ERROR,
|
||||
"critical": logging.CRITICAL}
|
||||
logging.basicConfig(level=mapping[level],
|
||||
format='%(asctime)s.%(msecs)03d %(levelname)8s %(name)20s: %(message)s',
|
||||
datefmt='%H:%M:%S')
|
||||
|
||||
logger = logging.getLogger("main")
|
||||
logger.debug("logging at level %s", logging.getLevelName(level))
|
||||
logger.info("using Python %i.%i.%i %s %i", *sys.version_info)
|
||||
|
||||
|
||||
def _init_log_option(parser):
|
||||
choices = ["none", "debug", "info", "warning", "error", "critical"]
|
||||
parser.add_option("--log-level", "-l",
|
||||
type="choice",
|
||||
choices=choices,
|
||||
action="store",
|
||||
dest="log_level",
|
||||
default="none",
|
||||
help=_("Enable logging, possible values: ") + ", ".join(choices))
|
||||
return parser
|
||||
|
||||
|
||||
def main(main_function, option_parser, gettext_domain=None, paths=None):
|
||||
|
||||
# FIXME:
|
||||
global Paths
|
||||
Paths = paths
|
||||
|
||||
_init_excepthooks()
|
||||
_init_paths(paths)
|
||||
_init_locale(gettext_domain)
|
||||
parser = _init_log_option(option_parser)
|
||||
options, args = option_parser.parse_args()
|
||||
_init_logging(options.log_level)
|
||||
|
||||
try:
|
||||
main_function(args)
|
||||
finally:
|
||||
logging.shutdown()
|
25
debug-viewer/GstDebugViewer/Common/__init__.py
Normal file
25
debug-viewer/GstDebugViewer/Common/__init__.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Development Utilities
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Development Utilities Common package."""
|
||||
|
||||
from . import Data
|
||||
from . import GUI
|
||||
from . import Main
|
||||
from . import utils
|
420
debug-viewer/GstDebugViewer/Common/generictreemodel.py
Normal file
420
debug-viewer/GstDebugViewer/Common/generictreemodel.py
Normal file
|
@ -0,0 +1,420 @@
|
|||
# -*- Mode: Python; py-indent-offset: 4 -*-
|
||||
# generictreemodel - GenericTreeModel implementation for pygtk compatibility.
|
||||
# Copyright (C) 2013 Simon Feltman
|
||||
#
|
||||
# generictreemodel.py: GenericTreeModel implementation for pygtk compatibility
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
# System
|
||||
import sys
|
||||
import random
|
||||
import collections
|
||||
import ctypes
|
||||
|
||||
# GObject
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class _CTreeIter(ctypes.Structure):
|
||||
_fields_ = [('stamp', ctypes.c_int),
|
||||
('user_data', ctypes.c_void_p),
|
||||
('user_data2', ctypes.c_void_p),
|
||||
('user_data3', ctypes.c_void_p)]
|
||||
|
||||
@classmethod
|
||||
def from_iter(cls, iter):
|
||||
offset = sys.getsizeof(object()) # size of PyObject_HEAD
|
||||
return ctypes.POINTER(cls).from_address(id(iter) + offset)
|
||||
|
||||
|
||||
def _get_user_data_as_pyobject(iter):
|
||||
citer = _CTreeIter.from_iter(iter)
|
||||
return ctypes.cast(citer.contents.user_data, ctypes.py_object).value
|
||||
|
||||
|
||||
def handle_exception(default_return):
|
||||
"""Returns a function which can act as a decorator for wrapping exceptions and
|
||||
returning "default_return" upon an exception being thrown.
|
||||
|
||||
This is used to wrap Gtk.TreeModel "do_" method implementations so we can return
|
||||
a proper value from the override upon an exception occurring with client code
|
||||
implemented by the "on_" methods.
|
||||
"""
|
||||
def decorator(func):
|
||||
def wrapped_func(*args, **kargs):
|
||||
try:
|
||||
return func(*args, **kargs)
|
||||
except BaseException:
|
||||
# Use excepthook directly to avoid any printing to the screen
|
||||
# if someone installed an except hook.
|
||||
sys.excepthook(*sys.exc_info())
|
||||
return default_return
|
||||
return wrapped_func
|
||||
return decorator
|
||||
|
||||
|
||||
class GenericTreeModel(GObject.GObject, Gtk.TreeModel):
|
||||
|
||||
"""A base implementation of a Gtk.TreeModel for python.
|
||||
|
||||
The GenericTreeModel eases implementing the Gtk.TreeModel interface in Python.
|
||||
The class can be subclassed to provide a TreeModel implementation which works
|
||||
directly with Python objects instead of iterators.
|
||||
|
||||
All of the on_* methods should be overridden by subclasses to provide the
|
||||
underlying implementation a way to access custom model data. For the purposes of
|
||||
this API, all custom model data supplied or handed back through the overridable
|
||||
API will use the argument names: node, parent, and child in regards to user data
|
||||
python objects.
|
||||
|
||||
The create_tree_iter, set_user_data, invalidate_iters, iter_is_valid methods are
|
||||
available to help manage Gtk.TreeIter objects and their Python object references.
|
||||
|
||||
GenericTreeModel manages a pool of user data nodes that have been used with iters.
|
||||
This pool stores a references to user data nodes as a dictionary value with the
|
||||
key being the integer id of the data. This id is what the Gtk.TreeIter objects
|
||||
use to reference data in the pool.
|
||||
References will be removed from the pool when the model is deleted or explicitly
|
||||
by using the optional "node" argument to the "row_deleted" method when notifying
|
||||
the model of row deletion.
|
||||
"""
|
||||
|
||||
leak_references = GObject.Property(default=True, type=bool,
|
||||
blurb="If True, strong references to user data attached to iters are "
|
||||
"stored in a dictionary pool (default). Otherwise the user data is "
|
||||
"stored as a raw pointer to a python object without a reference.")
|
||||
|
||||
#
|
||||
# Methods
|
||||
#
|
||||
def __init__(self):
|
||||
"""Initialize. Make sure to call this from derived classes if overridden."""
|
||||
super(GenericTreeModel, self).__init__()
|
||||
self.stamp = 0
|
||||
|
||||
#: Dictionary of (id(user_data): user_data), used when leak-refernces=False
|
||||
self._held_refs = dict()
|
||||
|
||||
# Set initial stamp
|
||||
self.invalidate_iters()
|
||||
|
||||
def iter_depth_first(self):
|
||||
"""Depth-first iteration of the entire TreeModel yielding the python nodes."""
|
||||
stack = collections.deque([None])
|
||||
while stack:
|
||||
it = stack.popleft()
|
||||
if it is not None:
|
||||
yield self.get_user_data(it)
|
||||
children = [self.iter_nth_child(it, i)
|
||||
for i in range(self.iter_n_children(it))]
|
||||
stack.extendleft(reversed(children))
|
||||
|
||||
def invalidate_iter(self, iter):
|
||||
"""Clear user data and its reference from the iter and this model."""
|
||||
iter.stamp = 0
|
||||
if iter.user_data:
|
||||
if iter.user_data in self._held_refs:
|
||||
del self._held_refs[iter.user_data]
|
||||
iter.user_data = None
|
||||
|
||||
def invalidate_iters(self):
|
||||
"""
|
||||
This method invalidates all TreeIter objects associated with this custom tree model
|
||||
and frees their locally pooled references.
|
||||
"""
|
||||
self.stamp = random.randint(-2147483648, 2147483647)
|
||||
self._held_refs.clear()
|
||||
|
||||
def iter_is_valid(self, iter):
|
||||
"""
|
||||
:Returns:
|
||||
True if the gtk.TreeIter specified by iter is valid for the custom tree model.
|
||||
"""
|
||||
return iter.stamp == self.stamp
|
||||
|
||||
def get_user_data(self, iter):
|
||||
"""Get the user_data associated with the given TreeIter.
|
||||
|
||||
GenericTreeModel stores arbitrary Python objects mapped to instances of Gtk.TreeIter.
|
||||
This method allows to retrieve the Python object held by the given iterator.
|
||||
"""
|
||||
if self.leak_references:
|
||||
return self._held_refs[iter.user_data]
|
||||
else:
|
||||
return _get_user_data_as_pyobject(iter)
|
||||
|
||||
def set_user_data(self, iter, user_data):
|
||||
"""Applies user_data and stamp to the given iter.
|
||||
|
||||
If the models "leak_references" property is set, a reference to the
|
||||
user_data is stored with the model to ensure we don't run into bad
|
||||
memory problems with the TreeIter.
|
||||
"""
|
||||
iter.user_data = id(user_data)
|
||||
|
||||
if user_data is None:
|
||||
self.invalidate_iter(iter)
|
||||
else:
|
||||
iter.stamp = self.stamp
|
||||
if self.leak_references:
|
||||
self._held_refs[iter.user_data] = user_data
|
||||
|
||||
def create_tree_iter(self, user_data):
|
||||
"""Create a Gtk.TreeIter instance with the given user_data specific for this model.
|
||||
|
||||
Use this method to create Gtk.TreeIter instance instead of directly calling
|
||||
Gtk.Treeiter(), this will ensure proper reference managment of wrapped used_data.
|
||||
"""
|
||||
iter = Gtk.TreeIter()
|
||||
self.set_user_data(iter, user_data)
|
||||
return iter
|
||||
|
||||
def _create_tree_iter(self, data):
|
||||
"""Internal creation of a (bool, TreeIter) pair for returning directly
|
||||
back to the view interfacing with this model."""
|
||||
if data is None:
|
||||
return (False, None)
|
||||
else:
|
||||
it = self.create_tree_iter(data)
|
||||
return (True, it)
|
||||
|
||||
def row_deleted(self, path, node=None):
|
||||
"""Notify the model a row has been deleted.
|
||||
|
||||
Use the node parameter to ensure the user_data reference associated
|
||||
with the path is properly freed by this model.
|
||||
|
||||
:Parameters:
|
||||
path : Gtk.TreePath
|
||||
Path to the row that has been deleted.
|
||||
node : object
|
||||
Python object used as the node returned from "on_get_iter". This is
|
||||
optional but ensures the model will not leak references to this object.
|
||||
"""
|
||||
super(GenericTreeModel, self).row_deleted(path)
|
||||
node_id = id(node)
|
||||
if node_id in self._held_refs:
|
||||
del self._held_refs[node_id]
|
||||
|
||||
#
|
||||
# GtkTreeModel Interface Implementation
|
||||
#
|
||||
@handle_exception(0)
|
||||
def do_get_flags(self):
|
||||
"""Internal method."""
|
||||
return self.on_get_flags()
|
||||
|
||||
@handle_exception(0)
|
||||
def do_get_n_columns(self):
|
||||
"""Internal method."""
|
||||
return self.on_get_n_columns()
|
||||
|
||||
@handle_exception(GObject.TYPE_INVALID)
|
||||
def do_get_column_type(self, index):
|
||||
"""Internal method."""
|
||||
return self.on_get_column_type(index)
|
||||
|
||||
@handle_exception((False, None))
|
||||
def do_get_iter(self, path):
|
||||
"""Internal method."""
|
||||
return self._create_tree_iter(self.on_get_iter(path))
|
||||
|
||||
@handle_exception(False)
|
||||
def do_iter_next(self, iter):
|
||||
"""Internal method."""
|
||||
if iter is None:
|
||||
next_data = self.on_iter_next(None)
|
||||
else:
|
||||
next_data = self.on_iter_next(self.get_user_data(iter))
|
||||
|
||||
self.set_user_data(iter, next_data)
|
||||
return next_data is not None
|
||||
|
||||
@handle_exception(None)
|
||||
def do_get_path(self, iter):
|
||||
"""Internal method."""
|
||||
path = self.on_get_path(self.get_user_data(iter))
|
||||
if path is None:
|
||||
return None
|
||||
else:
|
||||
return Gtk.TreePath(path)
|
||||
|
||||
@handle_exception(None)
|
||||
def do_get_value(self, iter, column):
|
||||
"""Internal method."""
|
||||
return self.on_get_value(self.get_user_data(iter), column)
|
||||
|
||||
@handle_exception((False, None))
|
||||
def do_iter_children(self, parent):
|
||||
"""Internal method."""
|
||||
data = self.get_user_data(parent) if parent else None
|
||||
return self._create_tree_iter(self.on_iter_children(data))
|
||||
|
||||
@handle_exception(False)
|
||||
def do_iter_has_child(self, parent):
|
||||
"""Internal method."""
|
||||
return self.on_iter_has_child(self.get_user_data(parent))
|
||||
|
||||
@handle_exception(0)
|
||||
def do_iter_n_children(self, iter):
|
||||
"""Internal method."""
|
||||
if iter is None:
|
||||
return self.on_iter_n_children(None)
|
||||
return self.on_iter_n_children(self.get_user_data(iter))
|
||||
|
||||
@handle_exception((False, None))
|
||||
def do_iter_nth_child(self, parent, n):
|
||||
"""Internal method."""
|
||||
if parent is None:
|
||||
data = self.on_iter_nth_child(None, n)
|
||||
else:
|
||||
data = self.on_iter_nth_child(self.get_user_data(parent), n)
|
||||
return self._create_tree_iter(data)
|
||||
|
||||
@handle_exception((False, None))
|
||||
def do_iter_parent(self, child):
|
||||
"""Internal method."""
|
||||
return self._create_tree_iter(self.on_iter_parent(self.get_user_data(child)))
|
||||
|
||||
@handle_exception(None)
|
||||
def do_ref_node(self, iter):
|
||||
self.on_ref_node(self.get_user_data(iter))
|
||||
|
||||
@handle_exception(None)
|
||||
def do_unref_node(self, iter):
|
||||
self.on_unref_node(self.get_user_data(iter))
|
||||
|
||||
#
|
||||
# Python Subclass Overridables
|
||||
#
|
||||
def on_get_flags(self):
|
||||
"""Overridable.
|
||||
|
||||
:Returns Gtk.TreeModelFlags:
|
||||
The flags for this model. See: Gtk.TreeModelFlags
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_get_n_columns(self):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
The number of columns for this model.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_get_column_type(self, index):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
The column type for the given index.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_get_iter(self, path):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
A python object (node) for the given TreePath.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_iter_next(self, node):
|
||||
"""Overridable.
|
||||
|
||||
:Parameters:
|
||||
node : object
|
||||
Node at current level.
|
||||
|
||||
:Returns:
|
||||
A python object (node) following the given node at the current level.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_get_path(self, node):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
A TreePath for the given node.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_get_value(self, node, column):
|
||||
"""Overridable.
|
||||
|
||||
:Parameters:
|
||||
node : object
|
||||
column : int
|
||||
Column index to get the value from.
|
||||
|
||||
:Returns:
|
||||
The value of the column for the given node."""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_iter_children(self, parent):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
The first child of parent or None if parent has no children.
|
||||
If parent is None, return the first node of the model.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_iter_has_child(self, node):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
True if the given node has children.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_iter_n_children(self, node):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
The number of children for the given node. If node is None,
|
||||
return the number of top level nodes.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_iter_nth_child(self, parent, n):
|
||||
"""Overridable.
|
||||
|
||||
:Parameters:
|
||||
parent : object
|
||||
n : int
|
||||
Index of child within parent.
|
||||
|
||||
:Returns:
|
||||
The child for the given parent index starting at 0. If parent None,
|
||||
return the top level node corresponding to "n".
|
||||
If "n" is larger then available nodes, return None.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_iter_parent(self, child):
|
||||
"""Overridable.
|
||||
|
||||
:Returns:
|
||||
The parent node of child or None if child is a top level node."""
|
||||
raise NotImplementedError
|
||||
|
||||
def on_ref_node(self, node):
|
||||
pass
|
||||
|
||||
def on_unref_node(self, node):
|
||||
pass
|
333
debug-viewer/GstDebugViewer/Common/utils.py
Normal file
333
debug-viewer/GstDebugViewer/Common/utils.py
Normal file
|
@ -0,0 +1,333 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Development Utilities
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Development Utilities Common utils module."""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import subprocess as _subprocess
|
||||
|
||||
|
||||
class SingletonMeta (type):
|
||||
|
||||
def __init__(cls, name, bases, dict_):
|
||||
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
super(SingletonMeta, cls).__init__(name, bases, dict_)
|
||||
|
||||
cls._singleton_instances = WeakValueDictionary()
|
||||
|
||||
def __call__(cls, *a, **kw):
|
||||
|
||||
kw_key = tuple(sorted(kw.items()))
|
||||
|
||||
try:
|
||||
obj = cls._singleton_instances[a + kw_key]
|
||||
except KeyError:
|
||||
obj = super(SingletonMeta, cls).__call__(*a, **kw)
|
||||
cls._singleton_instances[a + kw_key] = obj
|
||||
return obj
|
||||
|
||||
|
||||
def gettext_cache():
|
||||
"""Return a callable object that operates like gettext.gettext, but is much
|
||||
faster when a string is looked up more than once. This is very useful in
|
||||
loops, where calling gettext.gettext can quickly become a major performance
|
||||
bottleneck."""
|
||||
|
||||
from gettext import gettext
|
||||
|
||||
d = {}
|
||||
|
||||
def gettext_cache_access(s):
|
||||
|
||||
if s not in d:
|
||||
d[s] = gettext(s)
|
||||
return d[s]
|
||||
|
||||
return gettext_cache_access
|
||||
|
||||
|
||||
class ClassProperty (property):
|
||||
|
||||
"Like the property class, but also invokes the getter for class access."
|
||||
|
||||
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
|
||||
|
||||
property.__init__(self, fget, fset, fdel, doc)
|
||||
|
||||
self.__fget = fget
|
||||
|
||||
def __get__(self, obj, obj_class=None):
|
||||
|
||||
ret = property.__get__(self, obj, obj_class)
|
||||
if ret == self:
|
||||
return self.__fget(None)
|
||||
else:
|
||||
return ret
|
||||
|
||||
|
||||
class _XDGClass (object):
|
||||
|
||||
"""Partial implementation of the XDG Base Directory specification v0.6.
|
||||
|
||||
http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html"""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self._add_base_dir("DATA_HOME", "~/.local/share")
|
||||
self._add_base_dir("CONFIG_HOME", "~/.config")
|
||||
self._add_base_dir("CACHE_HOME", "~/.cache")
|
||||
|
||||
def _add_base_dir(self, name, default):
|
||||
|
||||
dir = os.environ.get("XDG_%s" % (name,))
|
||||
if not dir:
|
||||
dir = os.path.expanduser(os.path.join(*default.split("/")))
|
||||
|
||||
setattr(self, name, dir)
|
||||
|
||||
|
||||
XDG = _XDGClass()
|
||||
|
||||
|
||||
class SaveWriteFile (object):
|
||||
|
||||
def __init__(self, filename, mode="wt"):
|
||||
|
||||
from tempfile import mkstemp
|
||||
|
||||
self.logger = logging.getLogger("tempfile")
|
||||
|
||||
dir = os.path.dirname(filename)
|
||||
base_name = os.path.basename(filename)
|
||||
temp_prefix = "%s-tmp" % (base_name,)
|
||||
|
||||
if dir:
|
||||
# Destination dir differs from current directory, ensure that it
|
||||
# exists:
|
||||
try:
|
||||
os.makedirs(dir)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
self.clean_stale(dir, temp_prefix)
|
||||
|
||||
fd, temp_name = mkstemp(dir=dir, prefix=temp_prefix)
|
||||
|
||||
self.target_name = filename
|
||||
self.temp_name = temp_name
|
||||
self.real_file = os.fdopen(fd, mode)
|
||||
|
||||
def __enter__(self):
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc_args):
|
||||
|
||||
if exc_args == (None, None, None,):
|
||||
self.close()
|
||||
else:
|
||||
self.discard()
|
||||
|
||||
def __del__(self):
|
||||
|
||||
try:
|
||||
self.discard()
|
||||
except AttributeError:
|
||||
# If __init__ failed, self has no real_file attribute.
|
||||
pass
|
||||
|
||||
def __close_real(self):
|
||||
|
||||
if self.real_file:
|
||||
self.real_file.close()
|
||||
self.real_file = None
|
||||
|
||||
def clean_stale(self, dir, temp_prefix):
|
||||
|
||||
from time import time
|
||||
from glob import glob
|
||||
|
||||
now = time()
|
||||
pattern = os.path.join(dir, "%s*" % (temp_prefix,))
|
||||
|
||||
for temp_filename in glob(pattern):
|
||||
mtime = os.stat(temp_filename).st_mtime
|
||||
if now - mtime > 3600:
|
||||
self.logger.info("deleting stale temporary file %s",
|
||||
temp_filename)
|
||||
try:
|
||||
os.unlink(temp_filename)
|
||||
except EnvironmentError as exc:
|
||||
self.logger.warning("deleting stale temporary file "
|
||||
"failed: %s", exc)
|
||||
|
||||
def tell(self, *a, **kw):
|
||||
|
||||
return self.real_file.tell(*a, **kw)
|
||||
|
||||
def write(self, *a, **kw):
|
||||
|
||||
return self.real_file.write(*a, **kw)
|
||||
|
||||
def close(self):
|
||||
|
||||
self.__close_real()
|
||||
|
||||
if self.temp_name:
|
||||
try:
|
||||
os.rename(self.temp_name, self.target_name)
|
||||
except OSError as exc:
|
||||
import errno
|
||||
if exc.errno == errno.EEXIST:
|
||||
# We are probably on windows.
|
||||
os.unlink(self.target_name)
|
||||
os.rename(self.temp_name, self.target_name)
|
||||
self.temp_name = None
|
||||
|
||||
def discard(self):
|
||||
|
||||
self.__close_real()
|
||||
|
||||
if self.temp_name:
|
||||
|
||||
try:
|
||||
os.unlink(self.temp_name)
|
||||
except EnvironmentError as exc:
|
||||
self.logger.warning("deleting temporary file failed: %s", exc)
|
||||
self.temp_name = None
|
||||
|
||||
|
||||
class TeeWriteFile (object):
|
||||
|
||||
# TODO Py2.5: Add context manager methods.
|
||||
|
||||
def __init__(self, *file_objects):
|
||||
|
||||
self.files = list(file_objects)
|
||||
|
||||
def close(self):
|
||||
|
||||
for file in self.files:
|
||||
file.close()
|
||||
|
||||
def flush(self):
|
||||
|
||||
for file in self.files:
|
||||
file.flush()
|
||||
|
||||
def write(self, string):
|
||||
|
||||
for file in self.files:
|
||||
file.write(string)
|
||||
|
||||
def writelines(self, lines):
|
||||
|
||||
for file in self.files:
|
||||
file.writelines(lines)
|
||||
|
||||
|
||||
class FixedPopen (_subprocess.Popen):
|
||||
|
||||
def __init__(self, args, **kw):
|
||||
|
||||
# Unconditionally specify all descriptors as redirected, to
|
||||
# work around Python bug #1358527 (which is triggered for
|
||||
# console-less applications on Windows).
|
||||
|
||||
close = []
|
||||
|
||||
for name in ("stdin", "stdout", "stderr",):
|
||||
target = kw.get(name)
|
||||
if not target:
|
||||
kw[name] = _subprocess.PIPE
|
||||
close.append(name)
|
||||
|
||||
_subprocess.Popen.__init__(self, args, **kw)
|
||||
|
||||
for name in close:
|
||||
fp = getattr(self, name)
|
||||
fp.close()
|
||||
setattr(self, name, None)
|
||||
|
||||
|
||||
class DevhelpError (EnvironmentError):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DevhelpUnavailableError (DevhelpError):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DevhelpClient (object):
|
||||
|
||||
def available(self):
|
||||
|
||||
try:
|
||||
self.version()
|
||||
except DevhelpUnavailableError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def version(self):
|
||||
|
||||
return self._invoke("--version")
|
||||
|
||||
def search(self, entry):
|
||||
|
||||
self._invoke_no_interact("-s", entry)
|
||||
|
||||
def _check_os_error(self, exc):
|
||||
|
||||
import errno
|
||||
if exc.errno == errno.ENOENT:
|
||||
raise DevhelpUnavailableError()
|
||||
|
||||
def _invoke(self, *args):
|
||||
|
||||
from subprocess import PIPE
|
||||
|
||||
try:
|
||||
proc = FixedPopen(("devhelp",) + args,
|
||||
stdout=PIPE)
|
||||
except OSError as exc:
|
||||
self._check_os_error(exc)
|
||||
raise
|
||||
|
||||
out, err = proc.communicate()
|
||||
|
||||
if proc.returncode is not None and proc.returncode != 0:
|
||||
raise DevhelpError("devhelp exited with status %i"
|
||||
% (proc.returncode,))
|
||||
return out
|
||||
|
||||
def _invoke_no_interact(self, *args):
|
||||
|
||||
from subprocess import PIPE
|
||||
|
||||
try:
|
||||
proc = FixedPopen(("devhelp",) + args)
|
||||
except OSError as exc:
|
||||
self._check_os_error(exc)
|
||||
raise
|
482
debug-viewer/GstDebugViewer/Data.py
Normal file
482
debug-viewer/GstDebugViewer/Data.py
Normal file
|
@ -0,0 +1,482 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer Data module."""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Nanosecond resolution (like Gst.SECOND)
|
||||
SECOND = 1000000000
|
||||
|
||||
|
||||
def time_args(ts):
|
||||
|
||||
secs = ts // SECOND
|
||||
|
||||
return "%i:%02i:%02i.%09i" % (secs // 60 ** 2,
|
||||
secs // 60 % 60,
|
||||
secs % 60,
|
||||
ts % SECOND,)
|
||||
|
||||
|
||||
def time_diff_args(time_diff):
|
||||
|
||||
if time_diff >= 0:
|
||||
sign = "+"
|
||||
else:
|
||||
sign = "-"
|
||||
|
||||
secs = abs(time_diff) // SECOND
|
||||
|
||||
return "%s%02i:%02i.%09i" % (sign,
|
||||
secs // 60,
|
||||
secs % 60,
|
||||
abs(time_diff) % SECOND,)
|
||||
|
||||
|
||||
def time_args_no_hours(ts):
|
||||
|
||||
secs = ts // SECOND
|
||||
|
||||
return "%02i:%02i.%09i" % (secs // 60,
|
||||
secs % 60,
|
||||
ts % SECOND,)
|
||||
|
||||
|
||||
def parse_time(st):
|
||||
"""Parse time strings that look like "0:00:00.0000000"."""
|
||||
|
||||
h, m, s = st.split(":")
|
||||
secs, subsecs = s.split(".")
|
||||
|
||||
return int((int(h) * 60 ** 2 + int(m) * 60) * SECOND) + \
|
||||
int(secs) * SECOND + int(subsecs)
|
||||
|
||||
|
||||
class DebugLevel (int):
|
||||
|
||||
__names = ["NONE", "ERROR", "WARN", "FIXME",
|
||||
"INFO", "DEBUG", "LOG", "TRACE", "MEMDUMP"]
|
||||
__instances = {}
|
||||
|
||||
def __new__(cls, level):
|
||||
|
||||
try:
|
||||
level_int = int(level)
|
||||
except (ValueError, TypeError,):
|
||||
try:
|
||||
level_int = cls.__names.index(level.upper())
|
||||
except ValueError:
|
||||
raise ValueError("no debug level named %r" % (level,))
|
||||
if level_int in cls.__instances:
|
||||
return cls.__instances[level_int]
|
||||
else:
|
||||
new_instance = int.__new__(cls, level_int)
|
||||
new_instance.name = cls.__names[level_int]
|
||||
cls.__instances[level_int] = new_instance
|
||||
return new_instance
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return "<%s %s (%i)>" % (type(self).__name__, self.__names[self], self,)
|
||||
|
||||
def higher_level(self):
|
||||
|
||||
if self == len(self.__names) - 1:
|
||||
raise ValueError("already the highest debug level")
|
||||
|
||||
return DebugLevel(self + 1)
|
||||
|
||||
def lower_level(self):
|
||||
|
||||
if self == 0:
|
||||
raise ValueError("already the lowest debug level")
|
||||
|
||||
return DebugLevel(self - 1)
|
||||
|
||||
|
||||
debug_level_none = DebugLevel("NONE")
|
||||
debug_level_error = DebugLevel("ERROR")
|
||||
debug_level_warning = DebugLevel("WARN")
|
||||
debug_level_info = DebugLevel("INFO")
|
||||
debug_level_debug = DebugLevel("DEBUG")
|
||||
debug_level_log = DebugLevel("LOG")
|
||||
debug_level_fixme = DebugLevel("FIXME")
|
||||
debug_level_trace = DebugLevel("TRACE")
|
||||
debug_level_memdump = DebugLevel("MEMDUMP")
|
||||
debug_levels = [debug_level_none,
|
||||
debug_level_trace,
|
||||
debug_level_fixme,
|
||||
debug_level_log,
|
||||
debug_level_debug,
|
||||
debug_level_info,
|
||||
debug_level_warning,
|
||||
debug_level_error,
|
||||
debug_level_memdump]
|
||||
|
||||
# For stripping color codes:
|
||||
_escape = re.compile(b"\x1b\\[[0-9;]*m")
|
||||
|
||||
|
||||
def strip_escape(s):
|
||||
|
||||
# FIXME: This can be optimized further!
|
||||
|
||||
while b"\x1b" in s:
|
||||
s = _escape.sub(b"", s)
|
||||
return s
|
||||
|
||||
|
||||
def default_log_line_regex_():
|
||||
|
||||
# "DEBUG "
|
||||
LEVEL = "([A-Z]+)\s*"
|
||||
# "0x8165430 "
|
||||
THREAD = r"(0x[0-9a-f]+)\s+" # r"\((0x[0-9a-f]+) - "
|
||||
# "0:00:00.777913000 "
|
||||
TIME = r"(\d+:\d\d:\d\d\.\d+)\s+"
|
||||
CATEGORY = "([A-Za-z0-9_-]+)\s+" # "GST_REFCOUNTING ", "flacdec "
|
||||
# " 3089 "
|
||||
PID = r"(\d+)\s*"
|
||||
FILENAME = r"([^:]*):"
|
||||
LINE = r"(\d+):"
|
||||
FUNCTION = "(~?[A-Za-z0-9_\s\*,\(\)]*):"
|
||||
# FIXME: When non-g(st)object stuff is logged with *_OBJECT (like
|
||||
# buffers!), the address is printed *without* <> brackets!
|
||||
OBJECT = "(?:<([^>]+)>)?"
|
||||
MESSAGE = "(.+)"
|
||||
|
||||
ANSI = "(?:\x1b\\[[0-9;]*m\\s*)*\\s*"
|
||||
|
||||
# New log format:
|
||||
expressions = [TIME, ANSI, PID, ANSI, THREAD, ANSI, LEVEL, ANSI,
|
||||
CATEGORY, FILENAME, LINE, FUNCTION, ANSI,
|
||||
OBJECT, ANSI, MESSAGE]
|
||||
# Old log format:
|
||||
# expressions = [LEVEL, THREAD, TIME, CATEGORY, PID, FILENAME, LINE,
|
||||
# FUNCTION, OBJECT, MESSAGE]
|
||||
|
||||
return expressions
|
||||
|
||||
|
||||
def default_log_line_regex():
|
||||
|
||||
return re.compile("".join(default_log_line_regex_()))
|
||||
|
||||
|
||||
class Producer (object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.consumers = []
|
||||
|
||||
def have_load_started(self):
|
||||
|
||||
for consumer in self.consumers:
|
||||
consumer.handle_load_started()
|
||||
|
||||
def have_load_finished(self):
|
||||
|
||||
for consumer in self.consumers:
|
||||
consumer.handle_load_finished()
|
||||
|
||||
|
||||
class SortHelper (object):
|
||||
|
||||
def __init__(self, fileobj, offsets):
|
||||
|
||||
self._gen = self.__gen(fileobj, offsets)
|
||||
next(self._gen)
|
||||
|
||||
# Override in the instance, for performance (this gets called in an
|
||||
# inner loop):
|
||||
self.find_insert_position = self._gen.send
|
||||
|
||||
@staticmethod
|
||||
def find_insert_position(insert_time_string):
|
||||
|
||||
# Stub for documentary purposes.
|
||||
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def __gen(fileobj, offsets):
|
||||
|
||||
from math import floor
|
||||
tell = fileobj.tell
|
||||
seek = fileobj.seek
|
||||
read = fileobj.read
|
||||
time_len = len(time_args(0))
|
||||
|
||||
# We remember the previous insertion point. This gives a nice speed up
|
||||
# for larger bubbles which are already sorted. TODO: In practice, log
|
||||
# lines only get out of order across threads. Need to check if it pays
|
||||
# to parse the thread here and maintain multiple insertion points for
|
||||
# heavily interleaved parts of the log.
|
||||
pos = 0
|
||||
pos_time_string = ""
|
||||
|
||||
insert_pos = None
|
||||
while True:
|
||||
insert_time_string = (yield insert_pos)
|
||||
|
||||
save_offset = tell()
|
||||
|
||||
if pos_time_string <= insert_time_string:
|
||||
lo = pos
|
||||
hi = len(offsets)
|
||||
else:
|
||||
lo = 0
|
||||
hi = pos
|
||||
|
||||
# This is a bisection search, except we don't cut the range in the
|
||||
# middle each time, but at the 90th percentile. This is because
|
||||
# logs are "mostly sorted", so the insertion point is much more
|
||||
# likely to be at the end anyways:
|
||||
while lo < hi:
|
||||
mid = int(floor(lo * 0.1 + hi * 0.9))
|
||||
seek(offsets[mid])
|
||||
mid_time_string = read(time_len)
|
||||
if insert_time_string.encode('utf8') < mid_time_string:
|
||||
hi = mid
|
||||
else:
|
||||
lo = mid + 1
|
||||
pos = lo
|
||||
# Caller will replace row at pos with the new one, so this is
|
||||
# correct:
|
||||
pos_time_string = insert_time_string
|
||||
|
||||
insert_pos = pos
|
||||
|
||||
seek(save_offset)
|
||||
|
||||
|
||||
class LineCache (Producer):
|
||||
"""
|
||||
offsets: file position for each line
|
||||
levels: the debug level for each line
|
||||
"""
|
||||
|
||||
_lines_per_iteration = 50000
|
||||
|
||||
def __init__(self, fileobj, dispatcher):
|
||||
|
||||
Producer.__init__(self)
|
||||
|
||||
self.logger = logging.getLogger("linecache")
|
||||
self.dispatcher = dispatcher
|
||||
|
||||
self.__fileobj = fileobj
|
||||
self.__fileobj.seek(0, 2)
|
||||
self.__file_size = self.__fileobj.tell()
|
||||
self.__fileobj.seek(0)
|
||||
|
||||
self.offsets = []
|
||||
self.levels = [] # FIXME
|
||||
|
||||
def start_loading(self):
|
||||
|
||||
self.logger.debug("dispatching load process")
|
||||
self.have_load_started()
|
||||
self.dispatcher(self.__process())
|
||||
|
||||
def get_progress(self):
|
||||
|
||||
return float(self.__fileobj.tell()) / self.__file_size
|
||||
|
||||
def __process(self):
|
||||
|
||||
offsets = self.offsets
|
||||
levels = self.levels
|
||||
|
||||
dict_levels = {"T": debug_level_trace, "F": debug_level_fixme,
|
||||
"L": debug_level_log, "D": debug_level_debug,
|
||||
"I": debug_level_info, "W": debug_level_warning,
|
||||
"E": debug_level_error, " ": debug_level_none,
|
||||
"M": debug_level_memdump, }
|
||||
ANSI = "(?:\x1b\\[[0-9;]*m)?"
|
||||
ANSI_PATTERN = r"\d:\d\d:\d\d\.\d+ " + ANSI + \
|
||||
r" *\d+" + ANSI + \
|
||||
r" +0x[0-9a-f]+ +" + ANSI + \
|
||||
r"([TFLDIEWM ])"
|
||||
BARE_PATTERN = ANSI_PATTERN.replace(ANSI, "")
|
||||
rexp_bare = re.compile(BARE_PATTERN)
|
||||
rexp_ansi = re.compile(ANSI_PATTERN)
|
||||
rexp = rexp_bare
|
||||
|
||||
# Moving attribute lookups out of the loop:
|
||||
readline = self.__fileobj.readline
|
||||
tell = self.__fileobj.tell
|
||||
rexp_match = rexp.match
|
||||
levels_append = levels.append
|
||||
offsets_append = offsets.append
|
||||
dict_levels_get = dict_levels.get
|
||||
|
||||
self.__fileobj.seek(0)
|
||||
limit = self._lines_per_iteration
|
||||
last_line = ""
|
||||
i = 0
|
||||
sort_helper = SortHelper(self.__fileobj, offsets)
|
||||
find_insert_position = sort_helper.find_insert_position
|
||||
while True:
|
||||
i += 1
|
||||
if i >= limit:
|
||||
i = 0
|
||||
yield True
|
||||
|
||||
offset = tell()
|
||||
line = readline().decode('utf-8', errors='replace')
|
||||
if not line:
|
||||
break
|
||||
match = rexp_match(line)
|
||||
if match is None:
|
||||
if rexp is rexp_ansi or "\x1b" not in line:
|
||||
continue
|
||||
|
||||
match = rexp_ansi.match(line)
|
||||
if match is None:
|
||||
continue
|
||||
# Switch to slower ANSI parsing:
|
||||
rexp = rexp_ansi
|
||||
rexp_match = rexp.match
|
||||
|
||||
# Timestamp is in the very beginning of the row, and can be sorted
|
||||
# by lexical comparison. That's why we don't bother parsing the
|
||||
# time to integer. We also don't have to take a substring here,
|
||||
# which would be a useless memcpy.
|
||||
if line >= last_line:
|
||||
levels_append(
|
||||
dict_levels_get(match.group(1), debug_level_none))
|
||||
offsets_append(offset)
|
||||
last_line = line
|
||||
else:
|
||||
pos = find_insert_position(line)
|
||||
levels.insert(
|
||||
pos, dict_levels_get(match.group(1), debug_level_none))
|
||||
offsets.insert(pos, offset)
|
||||
|
||||
self.have_load_finished()
|
||||
yield False
|
||||
|
||||
|
||||
class LogLine (list):
|
||||
|
||||
_line_regex = default_log_line_regex()
|
||||
|
||||
@classmethod
|
||||
def parse_full(cls, line_string):
|
||||
match = cls._line_regex.match(line_string.decode('utf8', errors='replace'))
|
||||
if match is None:
|
||||
# raise ValueError ("not a valid log line (%r)" % (line_string,))
|
||||
groups = [0, 0, 0, 0, "", "", 0, "", "", 0]
|
||||
return cls(groups)
|
||||
|
||||
line = cls(match.groups())
|
||||
# Timestamp.
|
||||
line[0] = parse_time(line[0])
|
||||
# PID.
|
||||
line[1] = int(line[1])
|
||||
# Thread.
|
||||
line[2] = int(line[2], 16)
|
||||
# Level (this is handled in LineCache).
|
||||
line[3] = 0
|
||||
# Line.
|
||||
line[6] = int(line[6])
|
||||
# Message start offset.
|
||||
line[9] = match.start(9 + 1)
|
||||
|
||||
for col_id in (4, # COL_CATEGORY
|
||||
5, # COL_FILENAME
|
||||
7, # COL_FUNCTION,
|
||||
8,): # COL_OBJECT
|
||||
line[col_id] = sys.intern(line[col_id] or "")
|
||||
|
||||
return line
|
||||
|
||||
|
||||
class LogLines (object):
|
||||
|
||||
def __init__(self, fileobj, line_cache):
|
||||
|
||||
self.__fileobj = fileobj
|
||||
self.__line_cache = line_cache
|
||||
|
||||
def __len__(self):
|
||||
|
||||
return len(self.__line_cache.offsets)
|
||||
|
||||
def __getitem__(self, line_index):
|
||||
|
||||
offset = self.__line_cache.offsets[line_index]
|
||||
self.__fileobj.seek(offset)
|
||||
line_string = self.__fileobj.readline()
|
||||
line = LogLine.parse_full(line_string)
|
||||
msg = line_string[line[-1]:]
|
||||
line[-1] = msg
|
||||
return line
|
||||
|
||||
def __iter__(self):
|
||||
|
||||
size = len(self)
|
||||
i = 0
|
||||
while i < size:
|
||||
yield self[i]
|
||||
i += 1
|
||||
|
||||
|
||||
class LogFile (Producer):
|
||||
|
||||
def __init__(self, filename, dispatcher):
|
||||
|
||||
import mmap
|
||||
|
||||
Producer.__init__(self)
|
||||
|
||||
self.logger = logging.getLogger("logfile")
|
||||
|
||||
self.path = os.path.normpath(os.path.abspath(filename))
|
||||
self.__real_fileobj = open(filename, "rb")
|
||||
self.fileobj = mmap.mmap(
|
||||
self.__real_fileobj.fileno(), 0, access=mmap.ACCESS_READ)
|
||||
self.line_cache = LineCache(self.fileobj, dispatcher)
|
||||
self.line_cache.consumers.append(self)
|
||||
|
||||
def start_loading(self):
|
||||
|
||||
self.logger.debug("starting load")
|
||||
self.line_cache.start_loading()
|
||||
|
||||
def get_load_progress(self):
|
||||
|
||||
return self.line_cache.get_progress()
|
||||
|
||||
def handle_load_started(self):
|
||||
|
||||
# Chain up to our consumers:
|
||||
self.have_load_started()
|
||||
|
||||
def handle_load_finished(self):
|
||||
self.logger.debug("finish loading")
|
||||
self.lines = LogLines(self.fileobj, self.line_cache)
|
||||
|
||||
# Chain up to our consumers:
|
||||
self.have_load_finished()
|
44
debug-viewer/GstDebugViewer/GUI/__init__.py
Normal file
44
debug-viewer/GstDebugViewer/GUI/__init__.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer GUI module."""
|
||||
|
||||
__author__ = u"René Stadler <mail@renestadler.de>"
|
||||
__version__ = "0.1"
|
||||
|
||||
import gi
|
||||
|
||||
from GstDebugViewer.GUI.app import App
|
||||
|
||||
|
||||
def main(args):
|
||||
|
||||
app = App()
|
||||
|
||||
# TODO: Once we support more than one window, open one window for each
|
||||
# supplied filename.
|
||||
window = app.windows[0]
|
||||
if len(args) > 0:
|
||||
window.set_log_file(args[0])
|
||||
|
||||
app.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
158
debug-viewer/GstDebugViewer/GUI/app.py
Normal file
158
debug-viewer/GstDebugViewer/GUI/app.py
Normal file
|
@ -0,0 +1,158 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer GUI module."""
|
||||
|
||||
import os.path
|
||||
|
||||
import gi
|
||||
gi.require_version('Gdk', '3.0')
|
||||
gi.require_version('Gtk', '3.0')
|
||||
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import Gtk
|
||||
|
||||
from GstDebugViewer import Common
|
||||
from GstDebugViewer.GUI.columns import ViewColumnManager
|
||||
from GstDebugViewer.GUI.window import Window
|
||||
|
||||
|
||||
class AppStateSection (Common.GUI.StateSection):
|
||||
|
||||
_name = "state"
|
||||
|
||||
geometry = Common.GUI.StateInt4("window-geometry")
|
||||
maximized = Common.GUI.StateBool("window-maximized")
|
||||
|
||||
column_order = Common.GUI.StateItemList("column-order", ViewColumnManager)
|
||||
columns_visible = Common.GUI.StateItemList(
|
||||
"columns-visible", ViewColumnManager)
|
||||
|
||||
zoom_level = Common.GUI.StateInt("zoom-level")
|
||||
|
||||
|
||||
class AppState (Common.GUI.State):
|
||||
|
||||
def __init__(self, *a, **kw):
|
||||
|
||||
Common.GUI.State.__init__(self, *a, **kw)
|
||||
|
||||
self.add_section_class(AppStateSection)
|
||||
|
||||
|
||||
class App (object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.attach()
|
||||
|
||||
def load_plugins(self):
|
||||
|
||||
from GstDebugViewer import Plugins
|
||||
|
||||
plugin_classes = list(
|
||||
Plugins.load([os.path.dirname(Plugins.__file__)]))
|
||||
self.plugins = []
|
||||
for plugin_class in plugin_classes:
|
||||
plugin = plugin_class(self)
|
||||
self.plugins.append(plugin)
|
||||
|
||||
def iter_plugin_features(self):
|
||||
|
||||
for plugin in self.plugins:
|
||||
for feature in plugin.features:
|
||||
yield feature
|
||||
|
||||
def attach(self):
|
||||
|
||||
config_home = Common.utils.XDG.CONFIG_HOME
|
||||
|
||||
state_filename = os.path.join(
|
||||
config_home, "gst-debug-viewer", "state")
|
||||
|
||||
self.state = AppState(state_filename)
|
||||
self.state_section = self.state.sections["state"]
|
||||
|
||||
self.load_plugins()
|
||||
|
||||
self.windows = []
|
||||
|
||||
# Apply custom widget stying
|
||||
# TODO: check for dark theme
|
||||
css = b"""
|
||||
@define-color normal_bg_color #FFFFFF;
|
||||
@define-color shade_bg_color shade(@normal_bg_color, 0.95);
|
||||
#log_view row:nth-child(even) {
|
||||
background-color: @normal_bg_color;
|
||||
}
|
||||
#log_view row:nth-child(odd) {
|
||||
background-color: @shade_bg_color;
|
||||
}
|
||||
#log_view row:selected {
|
||||
background-color: #4488FF;
|
||||
}
|
||||
#log_view {
|
||||
-GtkTreeView-horizontal-separator: 0;
|
||||
-GtkTreeView-vertical-separator: 1;
|
||||
outline-width: 0;
|
||||
outline-offset: 0;
|
||||
}
|
||||
"""
|
||||
|
||||
style_provider = Gtk.CssProvider()
|
||||
style_provider.load_from_data(css)
|
||||
|
||||
Gtk.StyleContext.add_provider_for_screen(
|
||||
Gdk.Screen.get_default(),
|
||||
style_provider,
|
||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||
)
|
||||
|
||||
self.open_window()
|
||||
|
||||
def detach(self):
|
||||
|
||||
# TODO: If we take over deferred saving from the inspector, specify now
|
||||
# = True here!
|
||||
self.state.save()
|
||||
|
||||
def run(self):
|
||||
|
||||
try:
|
||||
Common.Main.MainLoopWrapper(Gtk.main, Gtk.main_quit).run()
|
||||
except BaseException:
|
||||
raise
|
||||
else:
|
||||
self.detach()
|
||||
|
||||
def open_window(self):
|
||||
|
||||
self.windows.append(Window(self))
|
||||
|
||||
def close_window(self, window):
|
||||
|
||||
self.windows.remove(window)
|
||||
if not self.windows:
|
||||
# GtkTreeView takes some time to go down for large files. Let's block
|
||||
# until the window is hidden:
|
||||
GObject.idle_add(Gtk.main_quit)
|
||||
Gtk.main()
|
||||
|
||||
Gtk.main_quit()
|
162
debug-viewer/GstDebugViewer/GUI/colors.py
Normal file
162
debug-viewer/GstDebugViewer/GUI/colors.py
Normal file
|
@ -0,0 +1,162 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer GUI module."""
|
||||
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
|
||||
from GstDebugViewer import Data
|
||||
|
||||
|
||||
class Color (object):
|
||||
|
||||
def __init__(self, hex_24):
|
||||
|
||||
if hex_24.startswith("#"):
|
||||
s = hex_24[1:]
|
||||
else:
|
||||
s = hex_24
|
||||
|
||||
self._fields = tuple((int(hs, 16) for hs in (s[:2], s[2:4], s[4:],)))
|
||||
|
||||
def gdk_color(self):
|
||||
|
||||
return Gdk.color_parse(self.hex_string())
|
||||
|
||||
def hex_string(self):
|
||||
|
||||
return "#%02x%02x%02x" % self._fields
|
||||
|
||||
def float_tuple(self):
|
||||
|
||||
return tuple((float(x) / 255 for x in self._fields))
|
||||
|
||||
def byte_tuple(self):
|
||||
|
||||
return self._fields
|
||||
|
||||
def short_tuple(self):
|
||||
|
||||
return tuple((x << 8 for x in self._fields))
|
||||
|
||||
|
||||
class ColorPalette (object):
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
|
||||
try:
|
||||
return cls._instance
|
||||
except AttributeError:
|
||||
cls._instance = cls()
|
||||
return cls._instance
|
||||
|
||||
|
||||
class TangoPalette (ColorPalette):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
for name, r, g, b in [("black", 0, 0, 0,),
|
||||
("white", 255, 255, 255,),
|
||||
("butter1", 252, 233, 79),
|
||||
("butter2", 237, 212, 0),
|
||||
("butter3", 196, 160, 0),
|
||||
("chameleon1", 138, 226, 52),
|
||||
("chameleon2", 115, 210, 22),
|
||||
("chameleon3", 78, 154, 6),
|
||||
("orange1", 252, 175, 62),
|
||||
("orange2", 245, 121, 0),
|
||||
("orange3", 206, 92, 0),
|
||||
("skyblue1", 114, 159, 207),
|
||||
("skyblue2", 52, 101, 164),
|
||||
("skyblue3", 32, 74, 135),
|
||||
("plum1", 173, 127, 168),
|
||||
("plum2", 117, 80, 123),
|
||||
("plum3", 92, 53, 102),
|
||||
("chocolate1", 233, 185, 110),
|
||||
("chocolate2", 193, 125, 17),
|
||||
("chocolate3", 143, 89, 2),
|
||||
("scarletred1", 239, 41, 41),
|
||||
("scarletred2", 204, 0, 0),
|
||||
("scarletred3", 164, 0, 0),
|
||||
("aluminium1", 238, 238, 236),
|
||||
("aluminium2", 211, 215, 207),
|
||||
("aluminium3", 186, 189, 182),
|
||||
("aluminium4", 136, 138, 133),
|
||||
("aluminium5", 85, 87, 83),
|
||||
("aluminium6", 46, 52, 54)]:
|
||||
setattr(self, name, Color("%02x%02x%02x" % (r, g, b,)))
|
||||
|
||||
|
||||
class ColorTheme (object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.colors = {}
|
||||
|
||||
def add_color(self, key, *colors):
|
||||
|
||||
self.colors[key] = colors
|
||||
|
||||
|
||||
class LevelColorTheme (ColorTheme):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class LevelColorThemeTango (LevelColorTheme):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
LevelColorTheme.__init__(self)
|
||||
|
||||
p = TangoPalette.get()
|
||||
self.add_color(Data.debug_level_none, None, None, None)
|
||||
self.add_color(Data.debug_level_trace, p.black, p.aluminium2)
|
||||
self.add_color(Data.debug_level_fixme, p.black, p.butter3)
|
||||
self.add_color(Data.debug_level_log, p.black, p.plum1)
|
||||
self.add_color(Data.debug_level_debug, p.black, p.skyblue1)
|
||||
self.add_color(Data.debug_level_info, p.black, p.chameleon1)
|
||||
self.add_color(Data.debug_level_warning, p.black, p.orange1)
|
||||
self.add_color(Data.debug_level_error, p.white, p.scarletred1)
|
||||
self.add_color(Data.debug_level_memdump, p.white, p.aluminium3)
|
||||
|
||||
|
||||
class ThreadColorTheme (ColorTheme):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ThreadColorThemeTango (ThreadColorTheme):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
ThreadColorTheme.__init__(self)
|
||||
|
||||
t = TangoPalette.get()
|
||||
for i, color in enumerate([t.butter2,
|
||||
t.orange2,
|
||||
t.chocolate3,
|
||||
t.chameleon2,
|
||||
t.skyblue1,
|
||||
t.plum1,
|
||||
t.scarletred1,
|
||||
t.aluminium6]):
|
||||
self.add_color(i, color)
|
741
debug-viewer/GstDebugViewer/GUI/columns.py
Normal file
741
debug-viewer/GstDebugViewer/GUI/columns.py
Normal file
|
@ -0,0 +1,741 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer GUI module."""
|
||||
|
||||
import logging
|
||||
|
||||
from gi.repository import Gtk, GLib
|
||||
|
||||
from GstDebugViewer import Common, Data
|
||||
from GstDebugViewer.GUI.colors import LevelColorThemeTango
|
||||
from GstDebugViewer.GUI.models import LazyLogModel, LogModelBase
|
||||
|
||||
|
||||
def _(s):
|
||||
return s
|
||||
|
||||
# Sync with gst-inspector!
|
||||
|
||||
|
||||
class Column (object):
|
||||
|
||||
"""A single list view column, managed by a ColumnManager instance."""
|
||||
|
||||
name = None
|
||||
id = None
|
||||
label_header = None
|
||||
get_modify_func = None
|
||||
get_data_func = None
|
||||
get_sort_func = None
|
||||
|
||||
def __init__(self):
|
||||
|
||||
view_column = Gtk.TreeViewColumn(self.label_header)
|
||||
view_column.props.reorderable = True
|
||||
|
||||
self.view_column = view_column
|
||||
|
||||
|
||||
class SizedColumn (Column):
|
||||
|
||||
default_size = None
|
||||
|
||||
def compute_default_size(self):
|
||||
|
||||
return None
|
||||
|
||||
# Sync with gst-inspector?
|
||||
|
||||
|
||||
class TextColumn (SizedColumn):
|
||||
|
||||
font_family = None
|
||||
|
||||
def __init__(self):
|
||||
|
||||
Column.__init__(self)
|
||||
|
||||
column = self.view_column
|
||||
cell = Gtk.CellRendererText()
|
||||
column.pack_start(cell, True)
|
||||
|
||||
cell.props.yalign = 0.
|
||||
cell.props.ypad = 0
|
||||
|
||||
if self.font_family:
|
||||
cell.props.family = self.font_family
|
||||
cell.props.family_set = True
|
||||
|
||||
if self.get_data_func:
|
||||
data_func = self.get_data_func()
|
||||
assert data_func
|
||||
id_ = self.id
|
||||
if id_ is not None:
|
||||
def cell_data_func(column, cell, model, tree_iter, user_data):
|
||||
data_func(cell.props, model.get_value(tree_iter, id_))
|
||||
else:
|
||||
cell_data_func = data_func
|
||||
column.set_cell_data_func(cell, cell_data_func)
|
||||
elif not self.get_modify_func:
|
||||
column.add_attribute(cell, "text", self.id)
|
||||
else:
|
||||
self.update_modify_func(column, cell)
|
||||
|
||||
column.props.resizable = True
|
||||
|
||||
def update_modify_func(self, column, cell):
|
||||
|
||||
modify_func = self.get_modify_func()
|
||||
id_ = self.id
|
||||
|
||||
def cell_data_func(column, cell, model, tree_iter, user_data):
|
||||
cell.props.text = modify_func(model.get_value(tree_iter, id_))
|
||||
column.set_cell_data_func(cell, cell_data_func)
|
||||
|
||||
def compute_default_size(self):
|
||||
|
||||
values = self.get_values_for_size()
|
||||
if not values:
|
||||
return SizedColumn.compute_default_size(self)
|
||||
|
||||
cell = self.view_column.get_cells()[0]
|
||||
|
||||
if self.get_modify_func is not None:
|
||||
format = self.get_modify_func()
|
||||
else:
|
||||
def identity(x):
|
||||
return x
|
||||
format = identity
|
||||
max_width = 0
|
||||
for value in values:
|
||||
cell.props.text = format(value)
|
||||
x, y, w, h = self.view_column.cell_get_size()
|
||||
max_width = max(max_width, w)
|
||||
|
||||
return max_width
|
||||
|
||||
def get_values_for_size(self):
|
||||
|
||||
return ()
|
||||
|
||||
|
||||
class TimeColumn (TextColumn):
|
||||
|
||||
name = "time"
|
||||
label_header = _("Time")
|
||||
id = LazyLogModel.COL_TIME
|
||||
font_family = "monospace"
|
||||
|
||||
def __init__(self, *a, **kw):
|
||||
|
||||
self.base_time = 0
|
||||
|
||||
TextColumn.__init__(self, *a, **kw)
|
||||
|
||||
def get_modify_func(self):
|
||||
|
||||
if self.base_time:
|
||||
time_diff_args = Data.time_diff_args
|
||||
base_time = self.base_time
|
||||
|
||||
def format_time(value):
|
||||
return time_diff_args(value - base_time)
|
||||
else:
|
||||
time_args = Data.time_args
|
||||
|
||||
def format_time(value):
|
||||
# TODO: This is hard coded to omit hours.
|
||||
return time_args(value)[2:]
|
||||
|
||||
return format_time
|
||||
|
||||
def get_values_for_size(self):
|
||||
|
||||
values = [0]
|
||||
|
||||
return values
|
||||
|
||||
def set_base_time(self, base_time):
|
||||
|
||||
self.base_time = base_time
|
||||
|
||||
column = self.view_column
|
||||
cell = column.get_cells()[0]
|
||||
self.update_modify_func(column, cell)
|
||||
|
||||
|
||||
class LevelColumn (TextColumn):
|
||||
|
||||
name = "level"
|
||||
label_header = _("L")
|
||||
id = LazyLogModel.COL_LEVEL
|
||||
|
||||
def __init__(self):
|
||||
|
||||
TextColumn.__init__(self)
|
||||
|
||||
cell = self.view_column.get_cells()[0]
|
||||
cell.props.xalign = .5
|
||||
|
||||
@staticmethod
|
||||
def get_modify_func():
|
||||
|
||||
def format_level(value):
|
||||
return value.name[0]
|
||||
|
||||
return format_level
|
||||
|
||||
@staticmethod
|
||||
def get_data_func():
|
||||
|
||||
theme = LevelColorThemeTango()
|
||||
colors = dict((level, tuple((c.gdk_color()
|
||||
for c in theme.colors[level])),)
|
||||
for level in Data.debug_levels
|
||||
if level != Data.debug_level_none)
|
||||
|
||||
def level_data_func(cell_props, level):
|
||||
cell_props.text = level.name[0]
|
||||
if level in colors:
|
||||
cell_colors = colors[level]
|
||||
else:
|
||||
cell_colors = (None, None, None,)
|
||||
cell_props.foreground_gdk = cell_colors[0]
|
||||
cell_props.background_gdk = cell_colors[1]
|
||||
|
||||
return level_data_func
|
||||
|
||||
def get_values_for_size(self):
|
||||
|
||||
values = [Data.debug_level_log, Data.debug_level_debug,
|
||||
Data.debug_level_info, Data.debug_level_warning,
|
||||
Data.debug_level_error, Data.debug_level_memdump]
|
||||
|
||||
return values
|
||||
|
||||
|
||||
class PidColumn (TextColumn):
|
||||
|
||||
name = "pid"
|
||||
label_header = _("PID")
|
||||
id = LazyLogModel.COL_PID
|
||||
font_family = "monospace"
|
||||
|
||||
@staticmethod
|
||||
def get_modify_func():
|
||||
|
||||
return str
|
||||
|
||||
def get_values_for_size(self):
|
||||
|
||||
return ["999999"]
|
||||
|
||||
|
||||
class ThreadColumn (TextColumn):
|
||||
|
||||
name = "thread"
|
||||
label_header = _("Thread")
|
||||
id = LazyLogModel.COL_THREAD
|
||||
font_family = "monospace"
|
||||
|
||||
@staticmethod
|
||||
def get_modify_func():
|
||||
|
||||
def format_thread(value):
|
||||
return "0x%07x" % (value,)
|
||||
|
||||
return format_thread
|
||||
|
||||
def get_values_for_size(self):
|
||||
|
||||
return [int("ffffff", 16)]
|
||||
|
||||
|
||||
class CategoryColumn (TextColumn):
|
||||
|
||||
name = "category"
|
||||
label_header = _("Category")
|
||||
id = LazyLogModel.COL_CATEGORY
|
||||
|
||||
def get_values_for_size(self):
|
||||
|
||||
return ["GST_LONG_CATEGORY", "somelongelement"]
|
||||
|
||||
|
||||
class CodeColumn (TextColumn):
|
||||
|
||||
name = "code"
|
||||
label_header = _("Code")
|
||||
id = None
|
||||
|
||||
@staticmethod
|
||||
def get_data_func():
|
||||
|
||||
filename_id = LogModelBase.COL_FILENAME
|
||||
line_number_id = LogModelBase.COL_LINE_NUMBER
|
||||
|
||||
def filename_data_func(column, cell, model, tree_iter, user_data):
|
||||
args = model.get(tree_iter, filename_id, line_number_id)
|
||||
cell.props.text = "%s:%i" % args
|
||||
|
||||
return filename_data_func
|
||||
|
||||
def get_values_for_size(self):
|
||||
|
||||
return ["gstsomefilename.c:1234"]
|
||||
|
||||
|
||||
class FunctionColumn (TextColumn):
|
||||
|
||||
name = "function"
|
||||
label_header = _("Function")
|
||||
id = LazyLogModel.COL_FUNCTION
|
||||
|
||||
def get_values_for_size(self):
|
||||
|
||||
return ["gst_this_should_be_enough"]
|
||||
|
||||
|
||||
class ObjectColumn (TextColumn):
|
||||
|
||||
name = "object"
|
||||
label_header = _("Object")
|
||||
id = LazyLogModel.COL_OBJECT
|
||||
|
||||
def get_values_for_size(self):
|
||||
|
||||
return ["longobjectname00"]
|
||||
|
||||
|
||||
class MessageColumn (TextColumn):
|
||||
|
||||
name = "message"
|
||||
label_header = _("Message")
|
||||
id = None
|
||||
|
||||
def __init__(self, *a, **kw):
|
||||
|
||||
self.highlighters = {}
|
||||
|
||||
TextColumn.__init__(self, *a, **kw)
|
||||
|
||||
def get_data_func(self):
|
||||
|
||||
highlighters = self.highlighters
|
||||
id_ = LazyLogModel.COL_MESSAGE
|
||||
|
||||
def message_data_func(column, cell, model, tree_iter, user_data):
|
||||
|
||||
msg = model.get_value(tree_iter, id_).decode("utf8", errors="replace")
|
||||
|
||||
if not highlighters:
|
||||
cell.props.text = msg
|
||||
return
|
||||
|
||||
if len(highlighters) > 1:
|
||||
raise NotImplementedError("FIXME: Support more than one...")
|
||||
|
||||
highlighter = list(highlighters.values())[0]
|
||||
row = model[tree_iter]
|
||||
ranges = highlighter(row)
|
||||
if not ranges:
|
||||
cell.props.text = msg
|
||||
else:
|
||||
tags = []
|
||||
prev_end = 0
|
||||
end = None
|
||||
for start, end in ranges:
|
||||
if prev_end < start:
|
||||
tags.append(
|
||||
GLib.markup_escape_text(msg[prev_end:start]))
|
||||
msg_escape = GLib.markup_escape_text(msg[start:end])
|
||||
tags.append("<span foreground=\'#FFFFFF\'"
|
||||
" background=\'#0000FF\'>%s</span>" % (msg_escape,))
|
||||
prev_end = end
|
||||
if end is not None:
|
||||
tags.append(GLib.markup_escape_text(msg[end:]))
|
||||
cell.props.markup = "".join(tags)
|
||||
|
||||
return message_data_func
|
||||
|
||||
def get_values_for_size(self):
|
||||
|
||||
values = ["Just some good minimum size"]
|
||||
|
||||
return values
|
||||
|
||||
|
||||
class ColumnManager (Common.GUI.Manager):
|
||||
|
||||
column_classes = ()
|
||||
|
||||
@classmethod
|
||||
def iter_item_classes(cls):
|
||||
|
||||
return iter(cls.column_classes)
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.view = None
|
||||
self.actions = None
|
||||
self.zoom = 1.0
|
||||
self.__columns_changed_id = None
|
||||
self.columns = []
|
||||
self.column_order = list(self.column_classes)
|
||||
|
||||
self.action_group = Gtk.ActionGroup("ColumnActions")
|
||||
|
||||
def make_entry(col_class):
|
||||
return ("show-%s-column" % (col_class.name,),
|
||||
None,
|
||||
col_class.label_header,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
True,)
|
||||
|
||||
entries = [make_entry(cls) for cls in self.column_classes]
|
||||
self.action_group.add_toggle_actions(entries)
|
||||
|
||||
def iter_items(self):
|
||||
|
||||
return iter(self.columns)
|
||||
|
||||
def attach(self):
|
||||
|
||||
for col_class in self.column_classes:
|
||||
action = self.get_toggle_action(col_class)
|
||||
if action.props.active:
|
||||
self._add_column(col_class())
|
||||
action.connect("toggled",
|
||||
self.__handle_show_column_action_toggled,
|
||||
col_class.name)
|
||||
|
||||
self.__columns_changed_id = self.view.connect("columns-changed",
|
||||
self.__handle_view_columns_changed)
|
||||
|
||||
def detach(self):
|
||||
|
||||
if self.__columns_changed_id is not None:
|
||||
self.view.disconnect(self.__columns_changed_id)
|
||||
self.__columns_changed_id = None
|
||||
|
||||
def attach_sort(self):
|
||||
|
||||
sort_model = self.view.get_model()
|
||||
|
||||
# Inform the sorted tree model of any custom sorting functions.
|
||||
for col_class in self.column_classes:
|
||||
if col_class.get_sort_func:
|
||||
sort_func = col_class.get_sort_func()
|
||||
sort_model.set_sort_func(col_class.id, sort_func)
|
||||
|
||||
def enable_sort(self):
|
||||
|
||||
sort_model = self.view.get_model()
|
||||
|
||||
if sort_model:
|
||||
self.logger.debug("activating sort")
|
||||
sort_model.set_sort_column_id(*self.default_sort)
|
||||
self.default_sort = None
|
||||
else:
|
||||
self.logger.debug("not activating sort (no model set)")
|
||||
|
||||
def disable_sort(self):
|
||||
|
||||
self.logger.debug("deactivating sort")
|
||||
|
||||
sort_model = self.view.get_model()
|
||||
|
||||
self.default_sort = tree_sortable_get_sort_column_id(sort_model)
|
||||
|
||||
sort_model.set_sort_column_id(TREE_SORTABLE_UNSORTED_COLUMN_ID,
|
||||
Gtk.SortType.ASCENDING)
|
||||
|
||||
def set_zoom(self, scale):
|
||||
|
||||
for column in self.columns:
|
||||
cell = column.view_column.get_cells()[0]
|
||||
cell.props.scale = scale
|
||||
column.view_column.queue_resize()
|
||||
|
||||
self.zoom = scale
|
||||
|
||||
def set_base_time(self, base_time):
|
||||
|
||||
try:
|
||||
time_column = self.find_item(name=TimeColumn.name)
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
time_column.set_base_time(base_time)
|
||||
self.size_column(time_column)
|
||||
|
||||
def get_toggle_action(self, column_class):
|
||||
|
||||
action_name = "show-%s-column" % (column_class.name,)
|
||||
return self.action_group.get_action(action_name)
|
||||
|
||||
def get_initial_column_order(self):
|
||||
|
||||
return tuple(self.column_classes)
|
||||
|
||||
def _add_column(self, column):
|
||||
|
||||
name = column.name
|
||||
pos = self.__get_column_insert_position(column)
|
||||
|
||||
if self.view.props.fixed_height_mode:
|
||||
column.view_column.props.sizing = Gtk.TreeViewColumnSizing.FIXED
|
||||
|
||||
cell = column.view_column.get_cells()[0]
|
||||
cell.props.scale = self.zoom
|
||||
|
||||
self.columns.insert(pos, column)
|
||||
self.view.insert_column(column.view_column, pos)
|
||||
|
||||
def _remove_column(self, column):
|
||||
|
||||
self.columns.remove(column)
|
||||
self.view.remove_column(column.view_column)
|
||||
|
||||
def __get_column_insert_position(self, column):
|
||||
|
||||
col_class = self.find_item_class(name=column.name)
|
||||
pos = self.column_order.index(col_class)
|
||||
before = self.column_order[:pos]
|
||||
shown_names = [col.name for col in self.columns]
|
||||
for col_class in before:
|
||||
if col_class.name not in shown_names:
|
||||
pos -= 1
|
||||
return pos
|
||||
|
||||
def __iter_next_hidden(self, column_class):
|
||||
|
||||
pos = self.column_order.index(column_class)
|
||||
rest = self.column_order[pos + 1:]
|
||||
for next_class in rest:
|
||||
try:
|
||||
self.find_item(name=next_class.name)
|
||||
except KeyError:
|
||||
# No instance -- the column is hidden.
|
||||
yield next_class
|
||||
else:
|
||||
break
|
||||
|
||||
def __handle_show_column_action_toggled(self, toggle_action, name):
|
||||
|
||||
if toggle_action.props.active:
|
||||
try:
|
||||
# This should fail.
|
||||
column = self.find_item(name=name)
|
||||
except KeyError:
|
||||
col_class = self.find_item_class(name=name)
|
||||
self._add_column(col_class())
|
||||
else:
|
||||
# Out of sync for some reason.
|
||||
return
|
||||
else:
|
||||
try:
|
||||
column = self.find_item(name=name)
|
||||
except KeyError:
|
||||
# Out of sync for some reason.
|
||||
return
|
||||
else:
|
||||
self._remove_column(column)
|
||||
|
||||
def __handle_view_columns_changed(self, element_view):
|
||||
|
||||
view_columns = element_view.get_columns()
|
||||
new_visible = [self.find_item(view_column=column)
|
||||
for column in view_columns]
|
||||
|
||||
# We only care about reordering here.
|
||||
if len(new_visible) != len(self.columns):
|
||||
return
|
||||
|
||||
if new_visible != self.columns:
|
||||
|
||||
new_order = []
|
||||
for column in new_visible:
|
||||
col_class = self.find_item_class(name=column.name)
|
||||
new_order.append(col_class)
|
||||
new_order.extend(self.__iter_next_hidden(col_class))
|
||||
|
||||
names = (column.name for column in new_visible)
|
||||
self.logger.debug("visible columns reordered: %s",
|
||||
", ".join(names))
|
||||
|
||||
self.columns[:] = new_visible
|
||||
self.column_order[:] = new_order
|
||||
|
||||
|
||||
class ViewColumnManager (ColumnManager):
|
||||
|
||||
column_classes = (
|
||||
TimeColumn, LevelColumn, PidColumn, ThreadColumn, CategoryColumn,
|
||||
CodeColumn, FunctionColumn, ObjectColumn, MessageColumn,)
|
||||
|
||||
default_column_classes = (
|
||||
TimeColumn, LevelColumn, CategoryColumn, CodeColumn,
|
||||
FunctionColumn, ObjectColumn, MessageColumn,)
|
||||
|
||||
def __init__(self, state):
|
||||
|
||||
ColumnManager.__init__(self)
|
||||
|
||||
self.logger = logging.getLogger("ui.columns")
|
||||
|
||||
self.state = state
|
||||
|
||||
def attach(self, view):
|
||||
|
||||
self.view = view
|
||||
view.connect("notify::model", self.__handle_notify_model)
|
||||
|
||||
order = self.state.column_order
|
||||
if len(order) == len(self.column_classes):
|
||||
self.column_order[:] = order
|
||||
|
||||
visible = self.state.columns_visible
|
||||
if not visible:
|
||||
visible = self.default_column_classes
|
||||
for col_class in self.column_classes:
|
||||
action = self.get_toggle_action(col_class)
|
||||
action.props.active = (col_class in visible)
|
||||
|
||||
ColumnManager.attach(self)
|
||||
|
||||
self.columns_sized = False
|
||||
|
||||
def detach(self):
|
||||
|
||||
self.state.column_order = self.column_order
|
||||
self.state.columns_visible = self.columns
|
||||
|
||||
return ColumnManager.detach(self)
|
||||
|
||||
def set_zoom(self, scale):
|
||||
|
||||
ColumnManager.set_zoom(self, scale)
|
||||
|
||||
if self.view is None:
|
||||
return
|
||||
|
||||
# Timestamp and log level columns are pretty much fixed size, so resize
|
||||
# them back to default on zoom change:
|
||||
names = (TimeColumn.name,
|
||||
LevelColumn.name,
|
||||
PidColumn.name,
|
||||
ThreadColumn.name)
|
||||
for column in self.columns:
|
||||
if column.name in names:
|
||||
self.size_column(column)
|
||||
|
||||
def size_column(self, column):
|
||||
|
||||
if column.default_size is None:
|
||||
default_size = column.compute_default_size()
|
||||
else:
|
||||
default_size = column.default_size
|
||||
# FIXME: Abstract away fixed size setting in Column class!
|
||||
if default_size is None:
|
||||
# Dummy fallback:
|
||||
column.view_column.props.fixed_width = 50
|
||||
self.logger.warning(
|
||||
"%s column does not implement default size", column.name)
|
||||
else:
|
||||
column.view_column.props.fixed_width = default_size
|
||||
|
||||
def _add_column(self, column):
|
||||
|
||||
result = ColumnManager._add_column(self, column)
|
||||
self.size_column(column)
|
||||
return result
|
||||
|
||||
def _remove_column(self, column):
|
||||
|
||||
column.default_size = column.view_column.props.fixed_width
|
||||
return ColumnManager._remove_column(self, column)
|
||||
|
||||
def __handle_notify_model(self, view, gparam):
|
||||
|
||||
if self.columns_sized:
|
||||
# Already sized.
|
||||
return
|
||||
model = self.view.get_model()
|
||||
if model is None:
|
||||
return
|
||||
self.logger.debug("model changed, sizing columns")
|
||||
for column in self.iter_items():
|
||||
self.size_column(column)
|
||||
self.columns_sized = True
|
||||
|
||||
|
||||
class WrappingMessageColumn (MessageColumn):
|
||||
|
||||
def wrap_to_width(self, width):
|
||||
|
||||
col = self.view_column
|
||||
col.props.max_width = width
|
||||
col.get_cells()[0].props.wrap_width = width
|
||||
col.queue_resize()
|
||||
|
||||
|
||||
class LineViewColumnManager (ColumnManager):
|
||||
|
||||
column_classes = (TimeColumn, WrappingMessageColumn,)
|
||||
|
||||
def __init__(self):
|
||||
|
||||
ColumnManager.__init__(self)
|
||||
|
||||
def attach(self, window):
|
||||
|
||||
self.__size_update = None
|
||||
|
||||
self.view = window.widgets.line_view
|
||||
self.view.set_size_request(0, 0)
|
||||
self.view.connect_after("size-allocate", self.__handle_size_allocate)
|
||||
ColumnManager.attach(self)
|
||||
|
||||
def __update_sizes(self):
|
||||
|
||||
view_width = self.view.get_allocation().width
|
||||
if view_width == self.__size_update:
|
||||
# Prevent endless recursion.
|
||||
return
|
||||
|
||||
self.__size_update = view_width
|
||||
|
||||
col = self.find_item(name="time")
|
||||
other_width = col.view_column.props.width
|
||||
|
||||
try:
|
||||
col = self.find_item(name="message")
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
width = view_width - other_width
|
||||
col.wrap_to_width(width)
|
||||
|
||||
def __handle_size_allocate(self, self_, allocation):
|
||||
|
||||
self.__update_sizes()
|
114
debug-viewer/GstDebugViewer/GUI/filters.py
Normal file
114
debug-viewer/GstDebugViewer/GUI/filters.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer GUI module."""
|
||||
|
||||
from GstDebugViewer.GUI.models import LogModelBase
|
||||
|
||||
|
||||
def get_comparison_function(all_but_this):
|
||||
|
||||
if (all_but_this):
|
||||
return lambda x, y: x == y
|
||||
else:
|
||||
return lambda x, y: x != y
|
||||
|
||||
|
||||
class Filter (object):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DebugLevelFilter (Filter):
|
||||
|
||||
only_this, all_but_this, this_and_above = range(3)
|
||||
|
||||
def __init__(self, debug_level, mode=0):
|
||||
|
||||
col_id = LogModelBase.COL_LEVEL
|
||||
if mode == self.this_and_above:
|
||||
def comparison_function(x, y):
|
||||
return x < y
|
||||
else:
|
||||
comparison_function = get_comparison_function(
|
||||
mode == self.all_but_this)
|
||||
|
||||
def filter_func(row):
|
||||
return comparison_function(row[col_id], debug_level)
|
||||
self.filter_func = filter_func
|
||||
|
||||
|
||||
class CategoryFilter (Filter):
|
||||
|
||||
def __init__(self, category, all_but_this=False):
|
||||
|
||||
col_id = LogModelBase.COL_CATEGORY
|
||||
comparison_function = get_comparison_function(all_but_this)
|
||||
|
||||
def category_filter_func(row):
|
||||
return comparison_function(row[col_id], category)
|
||||
self.filter_func = category_filter_func
|
||||
|
||||
|
||||
class ObjectFilter (Filter):
|
||||
|
||||
def __init__(self, object_, all_but_this=False):
|
||||
|
||||
col_id = LogModelBase.COL_OBJECT
|
||||
comparison_function = get_comparison_function(all_but_this)
|
||||
|
||||
def object_filter_func(row):
|
||||
return comparison_function(row[col_id], object_)
|
||||
self.filter_func = object_filter_func
|
||||
|
||||
|
||||
class FunctionFilter (Filter):
|
||||
|
||||
def __init__(self, function_, all_but_this=False):
|
||||
|
||||
col_id = LogModelBase.COL_FUNCTION
|
||||
comparison_function = get_comparison_function(all_but_this)
|
||||
|
||||
def function_filter_func(row):
|
||||
return comparison_function(row[col_id], function_)
|
||||
self.filter_func = function_filter_func
|
||||
|
||||
|
||||
class ThreadFilter (Filter):
|
||||
|
||||
def __init__(self, thread_, all_but_this=False):
|
||||
|
||||
col_id = LogModelBase.COL_THREAD
|
||||
comparison_function = get_comparison_function(all_but_this)
|
||||
|
||||
def thread_filter_func(row):
|
||||
return comparison_function(row[col_id], thread_)
|
||||
self.filter_func = thread_filter_func
|
||||
|
||||
|
||||
class FilenameFilter (Filter):
|
||||
|
||||
def __init__(self, filename, all_but_this=False):
|
||||
|
||||
col_id = LogModelBase.COL_FILENAME
|
||||
comparison_function = get_comparison_function(all_but_this)
|
||||
|
||||
def filename_filter_func(row):
|
||||
return comparison_function(row[col_id], filename)
|
||||
self.filter_func = filename_filter_func
|
498
debug-viewer/GstDebugViewer/GUI/models.py
Normal file
498
debug-viewer/GstDebugViewer/GUI/models.py
Normal file
|
@ -0,0 +1,498 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer GUI module."""
|
||||
|
||||
from array import array
|
||||
from bisect import bisect_left
|
||||
import logging
|
||||
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
|
||||
from GstDebugViewer import Common, Data
|
||||
|
||||
|
||||
class LogModelBase (Common.GUI.GenericTreeModel, metaclass=Common.GUI.MetaModel):
|
||||
|
||||
columns = ("COL_TIME", GObject.TYPE_UINT64,
|
||||
"COL_PID", int,
|
||||
"COL_THREAD", GObject.TYPE_UINT64,
|
||||
"COL_LEVEL", object,
|
||||
"COL_CATEGORY", str,
|
||||
"COL_FILENAME", str,
|
||||
"COL_LINE_NUMBER", int,
|
||||
"COL_FUNCTION", str,
|
||||
"COL_OBJECT", str,
|
||||
"COL_MESSAGE", str,)
|
||||
|
||||
def __init__(self):
|
||||
|
||||
Common.GUI.GenericTreeModel.__init__(self)
|
||||
|
||||
# self.props.leak_references = False
|
||||
|
||||
self.line_offsets = array("I")
|
||||
self.line_levels = [] # FIXME: Not so nice!
|
||||
self.line_cache = {}
|
||||
|
||||
def ensure_cached(self, line_offset):
|
||||
|
||||
raise NotImplementedError("derived classes must override this method")
|
||||
|
||||
def access_offset(self, offset):
|
||||
|
||||
raise NotImplementedError("derived classes must override this method")
|
||||
|
||||
def iter_rows_offset(self):
|
||||
|
||||
ensure_cached = self.ensure_cached
|
||||
line_cache = self.line_cache
|
||||
line_levels = self.line_levels
|
||||
COL_LEVEL = self.COL_LEVEL
|
||||
COL_MESSAGE = self.COL_MESSAGE
|
||||
access_offset = self.access_offset
|
||||
|
||||
for i, offset in enumerate(self.line_offsets):
|
||||
ensure_cached(offset)
|
||||
row = line_cache[offset]
|
||||
# adjust special rows
|
||||
row[COL_LEVEL] = line_levels[i]
|
||||
msg_offset = row[COL_MESSAGE]
|
||||
row[COL_MESSAGE] = access_offset(offset + msg_offset)
|
||||
yield (row, offset,)
|
||||
row[COL_MESSAGE] = msg_offset
|
||||
|
||||
def on_get_flags(self):
|
||||
|
||||
flags = Gtk.TreeModelFlags.LIST_ONLY | Gtk.TreeModelFlags.ITERS_PERSIST
|
||||
|
||||
return flags
|
||||
|
||||
def on_get_n_columns(self):
|
||||
|
||||
return len(self.column_types)
|
||||
|
||||
def on_get_column_type(self, col_id):
|
||||
|
||||
return self.column_types[col_id]
|
||||
|
||||
def on_get_iter(self, path):
|
||||
|
||||
if not path:
|
||||
return
|
||||
|
||||
if len(path) > 1:
|
||||
# Flat model.
|
||||
return None
|
||||
|
||||
line_index = path[0]
|
||||
|
||||
if line_index > len(self.line_offsets) - 1:
|
||||
return None
|
||||
|
||||
return line_index
|
||||
|
||||
def on_get_path(self, rowref):
|
||||
|
||||
line_index = rowref
|
||||
|
||||
return (line_index,)
|
||||
|
||||
def on_get_value(self, line_index, col_id):
|
||||
|
||||
last_index = len(self.line_offsets) - 1
|
||||
|
||||
if line_index > last_index:
|
||||
return None
|
||||
|
||||
if col_id == self.COL_LEVEL:
|
||||
return self.line_levels[line_index]
|
||||
|
||||
line_offset = self.line_offsets[line_index]
|
||||
self.ensure_cached(line_offset)
|
||||
|
||||
value = self.line_cache[line_offset][col_id]
|
||||
if col_id == self.COL_MESSAGE:
|
||||
# strip whitespace + newline
|
||||
value = self.access_offset(line_offset + value).strip()
|
||||
elif col_id in (self.COL_TIME, self.COL_THREAD):
|
||||
value = GObject.Value(GObject.TYPE_UINT64, value)
|
||||
|
||||
return value
|
||||
|
||||
def get_value_range(self, col_id, start, stop):
|
||||
|
||||
if col_id != self.COL_LEVEL:
|
||||
raise NotImplementedError("XXX FIXME")
|
||||
|
||||
return self.line_levels[start:stop]
|
||||
|
||||
def on_iter_next(self, line_index):
|
||||
|
||||
last_index = len(self.line_offsets) - 1
|
||||
|
||||
if line_index >= last_index:
|
||||
return None
|
||||
else:
|
||||
return line_index + 1
|
||||
|
||||
def on_iter_children(self, parent):
|
||||
|
||||
return self.on_iter_nth_child(parent, 0)
|
||||
|
||||
def on_iter_has_child(self, rowref):
|
||||
|
||||
return False
|
||||
|
||||
def on_iter_n_children(self, rowref):
|
||||
|
||||
if rowref is not None:
|
||||
return 0
|
||||
|
||||
return len(self.line_offsets)
|
||||
|
||||
def on_iter_nth_child(self, parent, n):
|
||||
|
||||
last_index = len(self.line_offsets) - 1
|
||||
|
||||
if parent or n > last_index:
|
||||
return None
|
||||
|
||||
return n
|
||||
|
||||
def on_iter_parent(self, child):
|
||||
|
||||
return None
|
||||
|
||||
# def on_ref_node (self, rowref):
|
||||
|
||||
# pass
|
||||
|
||||
# def on_unref_node (self, rowref):
|
||||
|
||||
# pass
|
||||
|
||||
|
||||
class LazyLogModel (LogModelBase):
|
||||
|
||||
def __init__(self, log_obj=None):
|
||||
|
||||
LogModelBase.__init__(self)
|
||||
|
||||
self.__log_obj = log_obj
|
||||
|
||||
if log_obj:
|
||||
self.set_log(log_obj)
|
||||
|
||||
def set_log(self, log_obj):
|
||||
|
||||
self.__fileobj = log_obj.fileobj
|
||||
|
||||
self.line_cache.clear()
|
||||
self.line_offsets = log_obj.line_cache.offsets
|
||||
self.line_levels = log_obj.line_cache.levels
|
||||
|
||||
def access_offset(self, offset):
|
||||
|
||||
# TODO: Implement using one slice access instead of seek+readline.
|
||||
self.__fileobj.seek(offset)
|
||||
return self.__fileobj.readline()
|
||||
|
||||
def ensure_cached(self, line_offset):
|
||||
|
||||
if line_offset in self.line_cache:
|
||||
return
|
||||
|
||||
if len(self.line_cache) > 10000:
|
||||
self.line_cache.clear()
|
||||
|
||||
self.__fileobj.seek(line_offset)
|
||||
line = self.__fileobj.readline()
|
||||
|
||||
self.line_cache[line_offset] = Data.LogLine.parse_full(line)
|
||||
|
||||
|
||||
class FilteredLogModelBase (LogModelBase):
|
||||
|
||||
def __init__(self, super_model):
|
||||
|
||||
LogModelBase.__init__(self)
|
||||
|
||||
self.logger = logging.getLogger("filter-model-base")
|
||||
|
||||
self.super_model = super_model
|
||||
self.access_offset = super_model.access_offset
|
||||
self.ensure_cached = super_model.ensure_cached
|
||||
self.line_cache = super_model.line_cache
|
||||
|
||||
def line_index_to_super(self, line_index):
|
||||
|
||||
raise NotImplementedError("index conversion not supported")
|
||||
|
||||
def line_index_from_super(self, super_line_index):
|
||||
|
||||
raise NotImplementedError("index conversion not supported")
|
||||
|
||||
|
||||
class FilteredLogModel (FilteredLogModelBase):
|
||||
|
||||
def __init__(self, super_model):
|
||||
|
||||
FilteredLogModelBase.__init__(self, super_model)
|
||||
|
||||
self.logger = logging.getLogger("filtered-log-model")
|
||||
|
||||
self.filters = []
|
||||
self.reset()
|
||||
self.__active_process = None
|
||||
self.__filter_progress = 0.
|
||||
|
||||
def reset(self):
|
||||
|
||||
self.logger.debug("reset filter")
|
||||
|
||||
self.line_offsets = self.super_model.line_offsets
|
||||
self.line_levels = self.super_model.line_levels
|
||||
self.super_index = range(len(self.line_offsets))
|
||||
|
||||
del self.filters[:]
|
||||
|
||||
def __filter_process(self, filter):
|
||||
|
||||
YIELD_LIMIT = 10000
|
||||
|
||||
self.logger.debug("preparing new filter")
|
||||
new_line_offsets = array("I")
|
||||
new_line_levels = []
|
||||
new_super_index = array("I")
|
||||
level_id = self.COL_LEVEL
|
||||
func = filter.filter_func
|
||||
|
||||
def enum():
|
||||
i = 0
|
||||
for row, offset in self.iter_rows_offset():
|
||||
line_index = self.super_index[i]
|
||||
yield (line_index, row, offset,)
|
||||
i += 1
|
||||
self.logger.debug("running filter")
|
||||
progress = 0.
|
||||
progress_full = float(len(self))
|
||||
y = YIELD_LIMIT
|
||||
for i, row, offset in enum():
|
||||
if func(row):
|
||||
new_line_offsets.append(offset)
|
||||
new_line_levels.append(row[level_id])
|
||||
new_super_index.append(i)
|
||||
y -= 1
|
||||
if y == 0:
|
||||
progress += float(YIELD_LIMIT)
|
||||
self.__filter_progress = progress / progress_full
|
||||
y = YIELD_LIMIT
|
||||
yield True
|
||||
self.line_offsets = new_line_offsets
|
||||
self.line_levels = new_line_levels
|
||||
self.super_index = new_super_index
|
||||
self.logger.debug("filtering finished")
|
||||
|
||||
self.__filter_progress = 1.
|
||||
self.__handle_filter_process_finished()
|
||||
yield False
|
||||
|
||||
def add_filter(self, filter, dispatcher):
|
||||
|
||||
if self.__active_process is not None:
|
||||
raise ValueError("dispatched a filter process already")
|
||||
|
||||
self.logger.debug("adding filter")
|
||||
|
||||
self.filters.append(filter)
|
||||
|
||||
self.__dispatcher = dispatcher
|
||||
self.__active_process = self.__filter_process(filter)
|
||||
dispatcher(self.__active_process)
|
||||
|
||||
def abort_process(self):
|
||||
|
||||
if self.__active_process is None:
|
||||
raise ValueError("no filter process running")
|
||||
|
||||
self.__dispatcher.cancel()
|
||||
self.__active_process = None
|
||||
self.__dispatcher = None
|
||||
|
||||
del self.filters[-1]
|
||||
|
||||
def get_filter_progress(self):
|
||||
|
||||
if self.__active_process is None:
|
||||
raise ValueError("no filter process running")
|
||||
|
||||
return self.__filter_progress
|
||||
|
||||
def __handle_filter_process_finished(self):
|
||||
|
||||
self.__active_process = None
|
||||
self.handle_process_finished()
|
||||
|
||||
def handle_process_finished(self):
|
||||
|
||||
pass
|
||||
|
||||
def line_index_from_super(self, super_line_index):
|
||||
|
||||
return bisect_left(self.super_index, super_line_index)
|
||||
|
||||
def line_index_to_super(self, line_index):
|
||||
|
||||
return self.super_index[line_index]
|
||||
|
||||
def set_range(self, super_start, super_stop):
|
||||
|
||||
old_super_start = self.line_index_to_super(0)
|
||||
old_super_stop = self.line_index_to_super(
|
||||
len(self.super_index) - 1) + 1
|
||||
|
||||
self.logger.debug("set range (%i, %i), current (%i, %i)",
|
||||
super_start, super_stop, old_super_start, old_super_stop)
|
||||
|
||||
if len(self.filters) == 0:
|
||||
# Identity.
|
||||
self.super_index = range(super_start, super_stop)
|
||||
self.line_offsets = SubRange(self.super_model.line_offsets,
|
||||
super_start, super_stop)
|
||||
self.line_levels = SubRange(self.super_model.line_levels,
|
||||
super_start, super_stop)
|
||||
return
|
||||
|
||||
if super_start < old_super_start:
|
||||
# TODO:
|
||||
raise NotImplementedError("Only handling further restriction of the range"
|
||||
" (start offset = %i)" % (super_start,))
|
||||
|
||||
if super_stop > old_super_stop:
|
||||
# TODO:
|
||||
raise NotImplementedError("Only handling further restriction of the range"
|
||||
" (end offset = %i)" % (super_stop,))
|
||||
|
||||
start = self.line_index_from_super(super_start)
|
||||
stop = self.line_index_from_super(super_stop)
|
||||
|
||||
self.super_index = SubRange(self.super_index, start, stop)
|
||||
self.line_offsets = SubRange(self.line_offsets, start, stop)
|
||||
self.line_levels = SubRange(self.line_levels, start, stop)
|
||||
|
||||
|
||||
class SubRange (object):
|
||||
|
||||
__slots__ = ("size", "start", "stop",)
|
||||
|
||||
def __init__(self, size, start, stop):
|
||||
|
||||
if start > stop:
|
||||
raise ValueError(
|
||||
"need start <= stop (got %r, %r)" % (start, stop,))
|
||||
|
||||
if isinstance(size, type(self)):
|
||||
# Another SubRange, don't stack:
|
||||
start += size.start
|
||||
stop += size.start
|
||||
size = size.size
|
||||
|
||||
self.size = size
|
||||
self.start = start
|
||||
self.stop = stop
|
||||
|
||||
def __getitem__(self, i):
|
||||
|
||||
if isinstance(i, slice):
|
||||
stop = i.stop
|
||||
if stop >= 0:
|
||||
stop += self.start
|
||||
else:
|
||||
stop += self.stop
|
||||
|
||||
return self.size[i.start + self.start:stop]
|
||||
else:
|
||||
return self.size[i + self.start]
|
||||
|
||||
def __len__(self):
|
||||
|
||||
return self.stop - self.start
|
||||
|
||||
def __iter__(self):
|
||||
|
||||
size = self.size
|
||||
for i in range(self.start, self.stop):
|
||||
yield size[i]
|
||||
|
||||
|
||||
class LineViewLogModel (FilteredLogModelBase):
|
||||
|
||||
def __init__(self, super_model):
|
||||
|
||||
FilteredLogModelBase.__init__(self, super_model)
|
||||
|
||||
self.line_offsets = []
|
||||
self.line_levels = []
|
||||
|
||||
self.parent_indices = []
|
||||
|
||||
def reset(self):
|
||||
|
||||
del self.line_offsets[:]
|
||||
del self.line_levels[:]
|
||||
|
||||
def line_index_to_super(self, line_index):
|
||||
|
||||
return self.parent_indices[line_index]
|
||||
|
||||
def insert_line(self, position, super_line_index):
|
||||
|
||||
if position == -1:
|
||||
position = len(self.line_offsets)
|
||||
li = super_line_index
|
||||
self.line_offsets.insert(position, self.super_model.line_offsets[li])
|
||||
self.line_levels.insert(position, self.super_model.line_levels[li])
|
||||
self.parent_indices.insert(position, super_line_index)
|
||||
|
||||
path = (position,)
|
||||
tree_iter = self.get_iter(path)
|
||||
self.row_inserted(path, tree_iter)
|
||||
|
||||
def replace_line(self, line_index, super_line_index):
|
||||
|
||||
li = line_index
|
||||
self.line_offsets[li] = self.super_model.line_offsets[super_line_index]
|
||||
self.line_levels[li] = self.super_model.line_levels[super_line_index]
|
||||
self.parent_indices[li] = super_line_index
|
||||
|
||||
path = (line_index,)
|
||||
tree_iter = self.get_iter(path)
|
||||
self.row_changed(path, tree_iter)
|
||||
|
||||
def remove_line(self, line_index):
|
||||
|
||||
for l in (self.line_offsets,
|
||||
self.line_levels,
|
||||
self.parent_indices,):
|
||||
del l[line_index]
|
||||
|
||||
path = (line_index,)
|
||||
self.row_deleted(path)
|
1068
debug-viewer/GstDebugViewer/GUI/window.py
Normal file
1068
debug-viewer/GstDebugViewer/GUI/window.py
Normal file
File diff suppressed because it is too large
Load diff
61
debug-viewer/GstDebugViewer/Main.py
Normal file
61
debug-viewer/GstDebugViewer/Main.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer Main module."""
|
||||
|
||||
import sys
|
||||
import optparse
|
||||
from gettext import gettext as _, ngettext
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
from GstDebugViewer import GUI
|
||||
import GstDebugViewer.Common.Main
|
||||
Common = GstDebugViewer.Common
|
||||
|
||||
GETTEXT_DOMAIN = "gst-debug-viewer"
|
||||
|
||||
|
||||
def main_version(opt, value, parser, *args, **kwargs):
|
||||
|
||||
from GstDebugViewer import version
|
||||
|
||||
print("GStreamer Debug Viewer %s" % (version,))
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
class Paths (Common.Main.PathsProgramBase):
|
||||
|
||||
program_name = "gst-debug-viewer"
|
||||
|
||||
|
||||
def main():
|
||||
parser = optparse.OptionParser(
|
||||
_("%prog [OPTION...] [FILENAME]"),
|
||||
description=_("Display and analyze GStreamer debug log files"))
|
||||
parser.add_option("--version", "-v",
|
||||
action="callback",
|
||||
dest="version",
|
||||
callback=main_version,
|
||||
help=_("Display version and exit"))
|
||||
|
||||
Common.Main.main(main_function=GUI.main,
|
||||
option_parser=parser,
|
||||
gettext_domain=GETTEXT_DOMAIN,
|
||||
paths=Paths)
|
497
debug-viewer/GstDebugViewer/Plugins/FindBar.py
Normal file
497
debug-viewer/GstDebugViewer/Plugins/FindBar.py
Normal file
|
@ -0,0 +1,497 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer timeline widget plugin."""
|
||||
|
||||
import logging
|
||||
|
||||
from GstDebugViewer import Common, Data, GUI
|
||||
from GstDebugViewer.Plugins import FeatureBase, PluginBase, _N
|
||||
|
||||
from gettext import gettext as _
|
||||
from gi.repository import GObject, GLib
|
||||
from gi.repository import Gtk
|
||||
|
||||
|
||||
class SearchOperation (object):
|
||||
|
||||
def __init__(self, model, search_text, search_forward=True, start_position=None):
|
||||
|
||||
self.model = model
|
||||
if isinstance(search_text, str):
|
||||
self.search_text = search_text.encode('utf8')
|
||||
else:
|
||||
self.search_text = search_text
|
||||
self.search_forward = search_forward
|
||||
self.start_position = start_position
|
||||
|
||||
col_id = GUI.models.LogModelBase.COL_MESSAGE
|
||||
len_search_text = len(self.search_text)
|
||||
|
||||
def match_func(model_row):
|
||||
|
||||
message = model_row[col_id]
|
||||
if self.search_text in message:
|
||||
ranges = []
|
||||
start = 0
|
||||
while True:
|
||||
pos = message.find(self.search_text, start)
|
||||
if pos == -1:
|
||||
break
|
||||
ranges.append((pos, pos + len_search_text,))
|
||||
start = pos + len_search_text
|
||||
return ranges
|
||||
else:
|
||||
return ()
|
||||
|
||||
self.match_func = match_func
|
||||
|
||||
|
||||
class SearchSentinel (object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.dispatcher = Common.Data.GSourceDispatcher()
|
||||
self.cancelled = False
|
||||
|
||||
def run_for(self, operation):
|
||||
|
||||
self.dispatcher.cancel()
|
||||
self.dispatcher(self.__process(operation))
|
||||
self.cancelled = False
|
||||
|
||||
def abort(self):
|
||||
|
||||
self.dispatcher.cancel()
|
||||
self.cancelled = True
|
||||
|
||||
def __process(self, operation):
|
||||
|
||||
model = operation.model
|
||||
|
||||
if operation.start_position is not None:
|
||||
start_pos = operation.start_position
|
||||
elif operation.search_forward:
|
||||
start_pos = 0
|
||||
else:
|
||||
start_pos = len(model) - 1
|
||||
|
||||
start_iter = model.iter_nth_child(None, start_pos)
|
||||
|
||||
match_func = operation.match_func
|
||||
if operation.search_forward:
|
||||
iter_next = model.iter_next
|
||||
else:
|
||||
# FIXME: This is really ugly.
|
||||
nth_child = model.iter_nth_child
|
||||
|
||||
def iter_next_():
|
||||
for i in range(start_pos, -1, -1):
|
||||
yield nth_child(None, i)
|
||||
yield None
|
||||
it_ = iter_next_()
|
||||
|
||||
def iter_next(it):
|
||||
return it_.__next__()
|
||||
|
||||
YIELD_LIMIT = 1000
|
||||
i = YIELD_LIMIT
|
||||
tree_iter = start_iter
|
||||
while tree_iter and not self.cancelled:
|
||||
i -= 1
|
||||
if i == 0:
|
||||
yield True
|
||||
i = YIELD_LIMIT
|
||||
row = model[tree_iter]
|
||||
if match_func(row):
|
||||
self.handle_match_found(model, tree_iter)
|
||||
tree_iter = iter_next(tree_iter)
|
||||
|
||||
if not self.cancelled:
|
||||
self.handle_search_complete()
|
||||
yield False
|
||||
|
||||
def handle_match_found(self, model, tree_iter):
|
||||
|
||||
pass
|
||||
|
||||
def handle_search_complete(self):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class FindBarWidget (Gtk.HBox):
|
||||
|
||||
__status = {"no-match-found": _N("No match found"),
|
||||
"searching": _N("Searching...")}
|
||||
|
||||
def __init__(self, action_group):
|
||||
|
||||
GObject.GObject.__init__(self)
|
||||
|
||||
label = Gtk.Label(label=_("Find:"))
|
||||
self.pack_start(label, False, False, 2)
|
||||
|
||||
self.entry = Gtk.Entry()
|
||||
self.pack_start(self.entry, True, True, 0)
|
||||
|
||||
prev_action = action_group.get_action("goto-previous-search-result")
|
||||
prev_button = Gtk.Button()
|
||||
prev_button.set_related_action(prev_action)
|
||||
self.pack_start(prev_button, False, False, 0)
|
||||
|
||||
next_action = action_group.get_action("goto-next-search-result")
|
||||
next_button = Gtk.Button()
|
||||
next_button.set_related_action(next_action)
|
||||
self.pack_start(next_button, False, False, 0)
|
||||
|
||||
self.status_label = Gtk.Label()
|
||||
self.status_label.props.xalign = 0.
|
||||
self.status_label.props.use_markup = True
|
||||
self.pack_start(self.status_label, False, False, 6)
|
||||
self.__compute_status_size()
|
||||
self.status_label.connect("notify::style", self.__handle_notify_style)
|
||||
|
||||
self.show_all()
|
||||
|
||||
def __compute_status_size(self):
|
||||
|
||||
label = self.status_label
|
||||
old_markup = label.props.label
|
||||
label.set_size_request(-1, -1)
|
||||
max_width = 0
|
||||
try:
|
||||
for status in self.__status.values():
|
||||
self.__set_status(_(status))
|
||||
req = label.size_request()
|
||||
max_width = max(max_width, req.width)
|
||||
label.set_size_request(max_width, -1)
|
||||
finally:
|
||||
label.props.label = old_markup
|
||||
|
||||
def __handle_notify_style(self, *a, **kw):
|
||||
|
||||
self.__compute_status_size()
|
||||
|
||||
def __set_status(self, text):
|
||||
|
||||
markup = "<b>%s</b>" % (GLib.markup_escape_text(text),)
|
||||
|
||||
self.status_label.props.label = markup
|
||||
|
||||
def status_no_match_found(self):
|
||||
|
||||
self.__set_status(_(self.__status["no-match-found"]))
|
||||
|
||||
def status_searching(self):
|
||||
|
||||
self.__set_status(_(self.__status["searching"]))
|
||||
|
||||
def clear_status(self):
|
||||
|
||||
self.__set_status("")
|
||||
|
||||
|
||||
class FindBarFeature (FeatureBase):
|
||||
|
||||
def __init__(self, app):
|
||||
|
||||
FeatureBase.__init__(self, app)
|
||||
|
||||
self.logger = logging.getLogger("ui.findbar")
|
||||
|
||||
self.action_group = Gtk.ActionGroup("FindBarActions")
|
||||
self.action_group.add_toggle_actions([("show-find-bar",
|
||||
None,
|
||||
_("Find Bar"),
|
||||
"<Ctrl>F")])
|
||||
self.action_group.add_actions([("goto-next-search-result",
|
||||
None, _("Goto Next Match"),
|
||||
"<Ctrl>G"),
|
||||
("goto-previous-search-result",
|
||||
None, _("Goto Previous Match"),
|
||||
"<Ctrl><Shift>G")])
|
||||
|
||||
self.bar = None
|
||||
self.operation = None
|
||||
self.search_state = None
|
||||
self.next_match = None
|
||||
self.prev_match = None
|
||||
self.scroll_match = False
|
||||
|
||||
self.sentinel = SearchSentinel()
|
||||
self.sentinel.handle_match_found = self.handle_match_found
|
||||
self.sentinel.handle_search_complete = self.handle_search_complete
|
||||
|
||||
def scroll_view_to_line(self, line_index):
|
||||
|
||||
view = self.log_view
|
||||
|
||||
path = Gtk.TreePath((line_index,))
|
||||
|
||||
start_path, end_path = view.get_visible_range()
|
||||
|
||||
if path >= start_path and path <= end_path:
|
||||
self.logger.debug(
|
||||
"line index %i already visible, not scrolling", line_index)
|
||||
return
|
||||
|
||||
self.logger.debug("scrolling to line_index %i", line_index)
|
||||
view.scroll_to_cell(path, use_align=True, row_align=.5)
|
||||
|
||||
def handle_attach_window(self, window):
|
||||
|
||||
self.window = window
|
||||
|
||||
ui = window.ui_manager
|
||||
|
||||
ui.insert_action_group(self.action_group, 0)
|
||||
|
||||
self.log_view = window.log_view
|
||||
|
||||
self.merge_id = ui.new_merge_id()
|
||||
for name, action_name in [("ViewFindBar", "show-find-bar",),
|
||||
("ViewNextResult",
|
||||
"goto-next-search-result",),
|
||||
("ViewPrevResult", "goto-previous-search-result",)]:
|
||||
ui.add_ui(self.merge_id, "/menubar/ViewMenu/ViewMenuAdditions",
|
||||
name, action_name, Gtk.UIManagerItemType.MENUITEM, False)
|
||||
|
||||
box = window.widgets.vbox_view
|
||||
self.bar = FindBarWidget(self.action_group)
|
||||
box.pack_end(self.bar, False, False, 0)
|
||||
self.bar.hide()
|
||||
|
||||
action = self.action_group.get_action("show-find-bar")
|
||||
handler = self.handle_show_find_bar_action_toggled
|
||||
action.connect("toggled", handler)
|
||||
|
||||
action = self.action_group.get_action("goto-previous-search-result")
|
||||
handler = self.handle_goto_previous_search_result_action_activate
|
||||
action.props.sensitive = False
|
||||
action.connect("activate", handler)
|
||||
|
||||
action = self.action_group.get_action("goto-next-search-result")
|
||||
handler = self.handle_goto_next_search_result_action_activate
|
||||
action.props.sensitive = False
|
||||
action.connect("activate", handler)
|
||||
|
||||
self.bar.entry.connect("changed", self.handle_entry_changed)
|
||||
|
||||
def handle_detach_window(self, window):
|
||||
|
||||
self.window = None
|
||||
|
||||
window.ui_manager.remove_ui(self.merge_id)
|
||||
self.merge_id = None
|
||||
|
||||
def handle_show_find_bar_action_toggled(self, action):
|
||||
|
||||
if action.props.active:
|
||||
self.bar.show()
|
||||
self.bar.entry.grab_focus()
|
||||
self.update_search()
|
||||
else:
|
||||
try:
|
||||
column = self.window.column_manager.find_item(
|
||||
name="message")
|
||||
del column.highlighters[self]
|
||||
except KeyError:
|
||||
pass
|
||||
self.bar.clear_status()
|
||||
self.bar.hide()
|
||||
for action_name in ["goto-next-search-result",
|
||||
"goto-previous-search-result"]:
|
||||
self.action_group.get_action(
|
||||
action_name).props.sensitive = False
|
||||
|
||||
def handle_goto_previous_search_result_action_activate(self, action):
|
||||
|
||||
if self.prev_match is None:
|
||||
self.logger.warning("inconsistent action sensitivity")
|
||||
return
|
||||
|
||||
self.scroll_view_to_line(self.prev_match)
|
||||
self.prev_match = None
|
||||
|
||||
start_path = self.log_view.get_visible_range()[0]
|
||||
new_position = start_path[0] - 1
|
||||
self.start_search_operation(start_position=new_position,
|
||||
forward=False)
|
||||
|
||||
# FIXME
|
||||
|
||||
def handle_goto_next_search_result_action_activate(self, action):
|
||||
|
||||
if self.next_match is None:
|
||||
self.logger.warning("inconsistent action sensitivity")
|
||||
return
|
||||
|
||||
self.scroll_view_to_line(self.next_match)
|
||||
self.next_match = None
|
||||
|
||||
end_path = self.log_view.get_visible_range()[1]
|
||||
new_position = end_path[0] + 1
|
||||
self.start_search_operation(start_position=new_position,
|
||||
forward=True)
|
||||
# FIXME: Finish.
|
||||
|
||||
# model = self.log_view.get_model ()
|
||||
|
||||
# start_path, end_path = self.log_view.get_visible_range ()
|
||||
# start_index, end_index = start_path[0], end_path[0]
|
||||
|
||||
# for line_index in self.matches:
|
||||
# if line_index > end_index:
|
||||
# break
|
||||
# else:
|
||||
# return
|
||||
|
||||
# self.scroll_view_to_line (line_index)
|
||||
|
||||
def handle_entry_changed(self, entry):
|
||||
|
||||
self.update_search()
|
||||
|
||||
def update_search(self):
|
||||
|
||||
model = self.log_view.get_model()
|
||||
search_text = self.bar.entry.props.text
|
||||
column = self.window.column_manager.find_item(name="message")
|
||||
if search_text == "":
|
||||
self.logger.debug("search string set to '', aborting search")
|
||||
self.search_state = None
|
||||
self.next_match = None
|
||||
self.prev_match = None
|
||||
self.update_sensitivity()
|
||||
self.sentinel.abort()
|
||||
try:
|
||||
del column.highlighters[self]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
self.logger.debug("starting search for %r", search_text)
|
||||
self.next_match = None
|
||||
self.prev_match = None
|
||||
self.update_sensitivity()
|
||||
self.scroll_match = True
|
||||
|
||||
start_path = self.log_view.get_visible_range()[0]
|
||||
self.start_search_operation(
|
||||
search_text, start_position=start_path[0])
|
||||
self.bar.status_searching()
|
||||
column.highlighters[self] = self.operation.match_func
|
||||
|
||||
self.window.update_view()
|
||||
|
||||
def update_sensitivity(self):
|
||||
|
||||
for name, value in (("goto-next-search-result", self.next_match,),
|
||||
("goto-previous-search-result", self.prev_match,),):
|
||||
action = self.action_group.get_action(name)
|
||||
action.props.sensitive = (value is not None)
|
||||
|
||||
def start_search_operation(self, search_text=None, forward=True, start_position=None):
|
||||
|
||||
model = self.log_view.get_model()
|
||||
|
||||
if forward:
|
||||
self.search_state = "search-forward"
|
||||
if start_position is None:
|
||||
start_position = 0
|
||||
else:
|
||||
self.search_state = "search-backward"
|
||||
if start_position is None:
|
||||
start_position = len(model) - 1
|
||||
|
||||
if search_text is None:
|
||||
operation = self.operation
|
||||
if operation is None:
|
||||
raise ValueError(
|
||||
"search_text not given but have no previous search operation")
|
||||
search_text = operation.search_text
|
||||
|
||||
self.operation = SearchOperation(model, search_text,
|
||||
start_position=start_position,
|
||||
search_forward=forward)
|
||||
self.sentinel.run_for(self.operation)
|
||||
|
||||
def handle_match_found(self, model, tree_iter):
|
||||
|
||||
if self.search_state not in ("search-forward", "search-backward",):
|
||||
self.logger.warning(
|
||||
"inconsistent search state %r", self.search_state)
|
||||
return
|
||||
|
||||
line_index = model.get_path(tree_iter)[0]
|
||||
forward_search = (self.search_state == "search-forward")
|
||||
|
||||
if forward_search:
|
||||
self.logger.debug("forward search for %r matches line %i",
|
||||
self.operation.search_text, line_index)
|
||||
else:
|
||||
self.logger.debug("backward search for %r matches line %i",
|
||||
self.operation.search_text, line_index)
|
||||
|
||||
self.sentinel.abort()
|
||||
|
||||
if self.scroll_match:
|
||||
self.logger.debug("scrolling to matching line")
|
||||
self.scroll_view_to_line(line_index)
|
||||
# Now search for the next one:
|
||||
self.scroll_match = False
|
||||
# FIXME: Start with first line that is outside of the visible
|
||||
# range.
|
||||
self.start_search_operation(start_position=line_index + 1,
|
||||
forward=forward_search)
|
||||
else:
|
||||
if forward_search:
|
||||
self.next_match = line_index
|
||||
|
||||
self.search_state = "search-backward"
|
||||
self.start_search_operation(forward=False,
|
||||
start_position=line_index - 1)
|
||||
else:
|
||||
self.prev_match = line_index
|
||||
self.update_sensitivity()
|
||||
self.bar.clear_status()
|
||||
|
||||
def handle_search_complete(self):
|
||||
|
||||
if self.search_state == "search-forward":
|
||||
self.logger.debug("forward search for %r reached last line",
|
||||
self.operation.search_text)
|
||||
self.next_match = None
|
||||
elif self.search_state == "search-backward":
|
||||
self.logger.debug("backward search for %r reached first line",
|
||||
self.operation.search_text)
|
||||
self.prev_match = None
|
||||
else:
|
||||
self.logger.warning("inconsistent search state %r",
|
||||
self.search_state)
|
||||
return
|
||||
|
||||
self.update_sensitivity()
|
||||
if self.prev_match is None and self.next_match is None:
|
||||
self.bar.status_no_match_found()
|
||||
|
||||
|
||||
class Plugin (PluginBase):
|
||||
|
||||
features = (FindBarFeature,)
|
1099
debug-viewer/GstDebugViewer/Plugins/Timeline.py
Normal file
1099
debug-viewer/GstDebugViewer/Plugins/Timeline.py
Normal file
File diff suppressed because it is too large
Load diff
103
debug-viewer/GstDebugViewer/Plugins/__init__.py
Normal file
103
debug-viewer/GstDebugViewer/Plugins/__init__.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer Plugins package."""
|
||||
|
||||
__all__ = ["_", "_N", "FeatureBase", "PluginBase"]
|
||||
|
||||
import os.path
|
||||
|
||||
|
||||
def _N(s):
|
||||
return s
|
||||
|
||||
|
||||
def load(paths=()):
|
||||
|
||||
for path in paths:
|
||||
for plugin_module in _load_plugins(path):
|
||||
yield plugin_module.Plugin
|
||||
|
||||
|
||||
def _load_plugins(path):
|
||||
|
||||
import imp
|
||||
import glob
|
||||
|
||||
files = glob.glob(os.path.join(path, "*.py"))
|
||||
|
||||
for filename in files:
|
||||
|
||||
name = os.path.basename(os.path.splitext(filename)[0])
|
||||
if name == "__init__":
|
||||
continue
|
||||
fp, pathname, description = imp.find_module(name, [path])
|
||||
module = imp.load_module(name, fp, pathname, description)
|
||||
yield module
|
||||
|
||||
|
||||
class FeatureBase (object):
|
||||
|
||||
def __init__(self, app):
|
||||
|
||||
pass
|
||||
|
||||
def handle_attach_window(self, window):
|
||||
"""
|
||||
window: GstDebugViewer.GUI.window.Window
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def handle_attach_log_file(self, window, log_file):
|
||||
"""
|
||||
window: GstDebugViewer.GUI.window.Window
|
||||
log_file: GstDebugViewer.Data.LogFile
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def handle_detach_log_file(self, window, log_file):
|
||||
"""
|
||||
window: GstDebugViewer.GUI.window.Window
|
||||
log_file: GstDebugViewer.Data.LogFile
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def handle_detach_window(self, window):
|
||||
"""
|
||||
window: GstDebugViewer.GUI.window.Window
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class PluginBase (object):
|
||||
"""
|
||||
All plugins must implement a class called Plugin inheriting from PluginBase.
|
||||
They should place a tuple of features they export into 'features'. Each
|
||||
feature should be a subclass of FeatureBase.
|
||||
"""
|
||||
|
||||
features = ()
|
||||
|
||||
def __init__(self, app):
|
||||
|
||||
pass
|
29
debug-viewer/GstDebugViewer/__init__.py
Normal file
29
debug-viewer/GstDebugViewer/__init__.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer package."""
|
||||
|
||||
version = "@VERSION@"
|
||||
|
||||
if version.startswith('@'):
|
||||
version = 'master'
|
||||
|
||||
__version__ = version
|
||||
|
||||
from GstDebugViewer.Main import Paths, GETTEXT_DOMAIN, main as run # noqa
|
0
debug-viewer/GstDebugViewer/tests/__init__.py
Normal file
0
debug-viewer/GstDebugViewer/tests/__init__.py
Normal file
54
debug-viewer/GstDebugViewer/tests/create-test-log.py
Executable file
54
debug-viewer/GstDebugViewer/tests/create-test-log.py
Executable file
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
|
||||
def line_string(ts, pid, thread, level, category, filename, line, function,
|
||||
object_, message):
|
||||
|
||||
# Replicates gstreamer/gst/gstinfo.c:gst_debug_log_default.
|
||||
|
||||
# FIXME: Regarding object_, this doesn't fully replicate the formatting!
|
||||
return "%s %5d 0x%x %s %20s %s:%d:%s:<%s> %s" % (Data.time_args(ts), pid, thread,
|
||||
level.name.ljust(
|
||||
5), category,
|
||||
filename, line, function,
|
||||
object_, message,)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
sys.path.append(os.path.dirname(os.path.dirname(sys.argv[0])))
|
||||
|
||||
global Data
|
||||
from GstDebugViewer import Data
|
||||
|
||||
count = 100000
|
||||
|
||||
ts = 0
|
||||
pid = 12345
|
||||
thread = int("89abcdef", 16)
|
||||
level = Data.debug_level_log
|
||||
category = "GST_DUMMY"
|
||||
filename = "gstdummyfilename.c"
|
||||
file_line = 1
|
||||
function = "gst_dummy_function"
|
||||
object_ = "dummyobj0"
|
||||
message = "dummy message with no content"
|
||||
|
||||
levels = (Data.debug_level_log,
|
||||
Data.debug_level_debug,
|
||||
Data.debug_level_info,)
|
||||
|
||||
shift = 0
|
||||
for i in range(count):
|
||||
|
||||
ts = i * 10000
|
||||
shift += i % (count // 100)
|
||||
level = levels[(i + shift) % 3]
|
||||
print(line_string(ts, pid, thread, level, category, filename, file_line,
|
||||
function, object_, message))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
79
debug-viewer/GstDebugViewer/tests/performance.py
Executable file
79
debug-viewer/GstDebugViewer/tests/performance.py
Executable file
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer performance test program."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
from glob import glob
|
||||
import time
|
||||
|
||||
import gi
|
||||
|
||||
from gi.repository import GObject
|
||||
|
||||
from .. import Common, Data, GUI
|
||||
|
||||
|
||||
class TestParsingPerformance (object):
|
||||
|
||||
def __init__(self, filename):
|
||||
|
||||
self.main_loop = GObject.MainLoop()
|
||||
self.log_file = Data.LogFile(filename, Common.Data.DefaultDispatcher())
|
||||
self.log_file.consumers.append(self)
|
||||
|
||||
def start(self):
|
||||
|
||||
self.log_file.start_loading()
|
||||
|
||||
def handle_load_started(self):
|
||||
|
||||
self.start_time = time.time()
|
||||
|
||||
def handle_load_finished(self):
|
||||
|
||||
diff = time.time() - self.start_time
|
||||
print("line cache built in %0.1f ms" % (diff * 1000.,))
|
||||
|
||||
start_time = time.time()
|
||||
model = GUI.LazyLogModel(self.log_file)
|
||||
for row in model:
|
||||
pass
|
||||
diff = time.time() - start_time
|
||||
print("model iterated in %0.1f ms" % (diff * 1000.,))
|
||||
print("overall time spent: %0.1f s" % (time.time() - self.start_time,))
|
||||
|
||||
import resource
|
||||
rusage = resource.getrusage(resource.RUSAGE_SELF)
|
||||
print("time spent in user mode: %.2f s" % (rusage.ru_utime,))
|
||||
print("time spent in system mode: %.2f s" % (rusage.ru_stime,))
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
test = TestParsingPerformance(sys.argv[1])
|
||||
test.start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
281
debug-viewer/GstDebugViewer/tests/test_models.py
Executable file
281
debug-viewer/GstDebugViewer/tests/test_models.py
Executable file
|
@ -0,0 +1,281 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer test suite for the custom tree models."""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
from glob import glob
|
||||
|
||||
from unittest import TestCase, main as test_main
|
||||
|
||||
from .. import Common, Data
|
||||
from .. GUI.filters import CategoryFilter, Filter
|
||||
from .. GUI.models import (FilteredLogModel,
|
||||
LogModelBase,
|
||||
SubRange,)
|
||||
|
||||
|
||||
class TestSubRange (TestCase):
|
||||
|
||||
def test_len(self):
|
||||
|
||||
values = list(range(20))
|
||||
|
||||
sr = SubRange(values, 0, 20)
|
||||
self.assertEqual(len(sr), 20)
|
||||
|
||||
sr = SubRange(values, 10, 20)
|
||||
self.assertEqual(len(sr), 10)
|
||||
|
||||
sr = SubRange(values, 0, 10)
|
||||
self.assertEqual(len(sr), 10)
|
||||
|
||||
sr = SubRange(values, 5, 15)
|
||||
self.assertEqual(len(sr), 10)
|
||||
|
||||
def test_iter(self):
|
||||
|
||||
values = list(range(20))
|
||||
|
||||
sr = SubRange(values, 0, 20)
|
||||
self.assertEqual(list(sr), values)
|
||||
|
||||
sr = SubRange(values, 10, 20)
|
||||
self.assertEqual(list(sr), list(range(10, 20)))
|
||||
|
||||
sr = SubRange(values, 0, 10)
|
||||
self.assertEqual(list(sr), list(range(0, 10)))
|
||||
|
||||
sr = SubRange(values, 5, 15)
|
||||
self.assertEqual(list(sr), list(range(5, 15)))
|
||||
|
||||
|
||||
class Model (LogModelBase):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
LogModelBase.__init__(self)
|
||||
|
||||
for i in range(20):
|
||||
self.line_offsets.append(i * 100)
|
||||
self.line_levels.append(Data.debug_level_debug)
|
||||
|
||||
def ensure_cached(self, line_offset):
|
||||
|
||||
pid = line_offset // 100
|
||||
if pid % 2 == 0:
|
||||
category = b"EVEN"
|
||||
else:
|
||||
category = b"ODD"
|
||||
|
||||
line_fmt = (b"0:00:00.000000000 %5i 0x0000000 DEBUG "
|
||||
b"%20s dummy.c:1:dummy: dummy")
|
||||
line_str = line_fmt % (pid, category,)
|
||||
log_line = Data.LogLine.parse_full(line_str)
|
||||
self.line_cache[line_offset] = log_line
|
||||
|
||||
def access_offset(self, line_offset):
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
class IdentityFilter (Filter):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
def filter_func(row):
|
||||
return True
|
||||
self.filter_func = filter_func
|
||||
|
||||
|
||||
class RandomFilter (Filter):
|
||||
|
||||
def __init__(self, seed):
|
||||
|
||||
import random
|
||||
rand = random.Random()
|
||||
rand.seed(seed)
|
||||
|
||||
def filter_func(row):
|
||||
return rand.choice((True, False,))
|
||||
self.filter_func = filter_func
|
||||
|
||||
|
||||
class TestDynamicFilter (TestCase):
|
||||
|
||||
def test_unset_filter_rerange(self):
|
||||
|
||||
full_model = Model()
|
||||
filtered_model = FilteredLogModel(full_model)
|
||||
row_list = self.__row_list
|
||||
|
||||
self.assertEqual(row_list(full_model), list(range(20)))
|
||||
self.assertEqual(row_list(filtered_model), list(range(20)))
|
||||
|
||||
filtered_model.set_range(5, 16)
|
||||
|
||||
self.assertEqual(row_list(filtered_model), list(range(5, 16)))
|
||||
|
||||
def test_identity_filter_rerange(self):
|
||||
|
||||
full_model = Model()
|
||||
filtered_model = FilteredLogModel(full_model)
|
||||
row_list = self.__row_list
|
||||
|
||||
self.assertEqual(row_list(full_model), list(range(20)))
|
||||
self.assertEqual(row_list(filtered_model), list(range(20)))
|
||||
|
||||
filtered_model.add_filter(IdentityFilter(),
|
||||
Common.Data.DefaultDispatcher())
|
||||
filtered_model.set_range(5, 16)
|
||||
|
||||
self.assertEqual(row_list(filtered_model), list(range(5, 16)))
|
||||
|
||||
def test_filtered_range_refilter_skip(self):
|
||||
|
||||
full_model = Model()
|
||||
filtered_model = FilteredLogModel(full_model)
|
||||
|
||||
row_list = self.__row_list
|
||||
|
||||
filtered_model.add_filter(CategoryFilter("EVEN"),
|
||||
Common.Data.DefaultDispatcher())
|
||||
self.__dump_model(filtered_model, "filtered")
|
||||
|
||||
self.assertEqual(row_list(filtered_model), list(range(1, 20, 2)))
|
||||
self.assertEqual([filtered_model.line_index_from_super(i)
|
||||
for i in range(1, 20, 2)],
|
||||
list(range(10)))
|
||||
self.assertEqual([filtered_model.line_index_to_super(i)
|
||||
for i in range(10)],
|
||||
list(range(1, 20, 2)))
|
||||
|
||||
filtered_model.set_range(1, 20)
|
||||
self.__dump_model(filtered_model, "ranged (1, 20)")
|
||||
self.__dump_model(filtered_model, "filtered range")
|
||||
|
||||
self.assertEqual([filtered_model.line_index_from_super(i)
|
||||
for i in range(0, 19, 2)],
|
||||
list(range(10)))
|
||||
self.assertEqual([filtered_model.line_index_to_super(i)
|
||||
for i in range(10)],
|
||||
list(range(1, 20, 2)))
|
||||
|
||||
filtered_model.set_range(2, 20)
|
||||
self.__dump_model(filtered_model, "ranged (2, 20)")
|
||||
|
||||
self.assertEqual(row_list(filtered_model), list(range(3, 20, 2)))
|
||||
|
||||
def test_filtered_range_refilter(self):
|
||||
|
||||
full_model = Model()
|
||||
filtered_model = FilteredLogModel(full_model)
|
||||
|
||||
row_list = self.__row_list
|
||||
rows = row_list(full_model)
|
||||
rows_filtered = row_list(filtered_model)
|
||||
|
||||
self.__dump_model(full_model, "full model")
|
||||
|
||||
self.assertEqual(rows, rows_filtered)
|
||||
|
||||
self.assertEqual([filtered_model.line_index_from_super(i)
|
||||
for i in range(20)],
|
||||
list(range(20)))
|
||||
self.assertEqual([filtered_model.line_index_to_super(i)
|
||||
for i in range(20)],
|
||||
list(range(20)))
|
||||
|
||||
filtered_model.set_range(5, 16)
|
||||
self.__dump_model(filtered_model, "ranged model (5, 16)")
|
||||
|
||||
rows_ranged = row_list(filtered_model)
|
||||
self.assertEqual(rows_ranged, list(range(5, 16)))
|
||||
|
||||
self.__dump_model(filtered_model, "filtered model (nofilter, 5, 15)")
|
||||
|
||||
filtered_model.add_filter(CategoryFilter("EVEN"),
|
||||
Common.Data.DefaultDispatcher())
|
||||
rows_filtered = row_list(filtered_model)
|
||||
self.assertEqual(rows_filtered, list(range(5, 16, 2)))
|
||||
|
||||
self.__dump_model(filtered_model, "filtered model")
|
||||
|
||||
def test_random_filtered_range_refilter(self):
|
||||
|
||||
full_model = Model()
|
||||
filtered_model = FilteredLogModel(full_model)
|
||||
row_list = self.__row_list
|
||||
|
||||
self.assertEqual(row_list(full_model), list(range(20)))
|
||||
self.assertEqual(row_list(filtered_model), list(range(20)))
|
||||
|
||||
filtered_model.add_filter(RandomFilter(538295943),
|
||||
Common.Data.DefaultDispatcher())
|
||||
random_rows = row_list(filtered_model)
|
||||
|
||||
self.__dump_model(filtered_model)
|
||||
|
||||
filtered_model = FilteredLogModel(full_model)
|
||||
filtered_model.add_filter(RandomFilter(538295943),
|
||||
Common.Data.DefaultDispatcher())
|
||||
self.__dump_model(filtered_model, "filtered model")
|
||||
self.assertEqual(row_list(filtered_model), random_rows)
|
||||
|
||||
filtered_model.set_range(1, 10)
|
||||
self.__dump_model(filtered_model)
|
||||
self.assertEqual(row_list(filtered_model), [
|
||||
x for x in range(0, 10) if x in random_rows])
|
||||
|
||||
def __row_list(self, model):
|
||||
|
||||
return [row[Model.COL_PID] for row in model]
|
||||
|
||||
def __dump_model(self, model, comment=None):
|
||||
|
||||
# TODO: Provide a command line option to turn this on and off.
|
||||
|
||||
return
|
||||
|
||||
if not hasattr(model, "super_model"):
|
||||
# Top model.
|
||||
print("\t(%s)" % ("|".join([str(i).rjust(2)
|
||||
for i in self.__row_list(model)]),), end=' ')
|
||||
else:
|
||||
top_model = model.super_model
|
||||
if hasattr(top_model, "super_model"):
|
||||
top_model = top_model.super_model
|
||||
top_indices = self.__row_list(top_model)
|
||||
positions = self.__row_list(model)
|
||||
output = [" "] * len(top_indices)
|
||||
for i, position in enumerate(positions):
|
||||
output[position] = str(i).rjust(2)
|
||||
print("\t(%s)" % ("|".join(output),), end=' ')
|
||||
|
||||
if comment is None:
|
||||
print()
|
||||
else:
|
||||
print(comment)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_main()
|
9
debug-viewer/MANIFEST.in
Normal file
9
debug-viewer/MANIFEST.in
Normal file
|
@ -0,0 +1,9 @@
|
|||
recursive-include GstDebugViewer *.py
|
||||
recursive-include data *.glade *.ui *.svg *.png
|
||||
recursive-include po *.po
|
||||
recursive-include tests *.py
|
||||
include gst-debug-viewer
|
||||
include gst-debug-viewer.desktop.in
|
||||
include org.freedesktop.GstDebugViewer.appdata.xml.in
|
||||
include AUTHORS COPYING ChangeLog MANIFEST.in NEWS README TODO
|
||||
include po/POTFILES.in
|
36
debug-viewer/README
Normal file
36
debug-viewer/README
Normal file
|
@ -0,0 +1,36 @@
|
|||
# how to build #
|
||||
|
||||
./setup.py build; sudo ./setup.py install --prefix=/usr
|
||||
sudo chmod a+r /usr/share/gst-debug-viewer/*.ui
|
||||
|
||||
# porting issues #
|
||||
|
||||
http://stackoverflow.com/questions/11025700/generictreemodel-with-pygobject-introspection-gtk-3
|
||||
|
||||
# tips #
|
||||
|
||||
OLD: prev_action.connect_proxy(prev_button)
|
||||
NEW: prev_button.set_related_action (prev_action)
|
||||
|
||||
OLD: box.pack_start (widget)
|
||||
NEW: box.pack_start (widget, True, True, 0)
|
||||
|
||||
OLD: column.pack_start (cell)
|
||||
NEW: column.pack_start (cell, True)
|
||||
|
||||
OLD: view_column.get_cell_renderers ()
|
||||
NEW: column.get_cells ()
|
||||
|
||||
# porting ressources #
|
||||
https://www.xpra.org/trac/ticket/90?cversion=0&cnum_hist=3
|
||||
https://mail.gnome.org/archives/commits-list/2013-October/msg05205.html
|
||||
|
||||
# profiling #
|
||||
python -m profile -o output.pstats path/to/your/script arg1 arg2
|
||||
gprof2dot.py -f pstats output.pstats | dot -Tpng -o output.png
|
||||
~/projects/tools/gprof2dot/gprof2dot.py -f pstats output.pstats | dot -Tpng -o output.png
|
||||
eog output.png
|
||||
|
||||
python -m cProfile -o output.pstats2 ./gst-debug-viewer debug.noansi.log
|
||||
~/projects/tools/gprof2dot/gprof2dot.py -f pstats output.pstats2 | dot -Tpng -o output2.png
|
||||
eog output2.png
|
701
debug-viewer/data/about-dialog.ui
Normal file
701
debug-viewer/data/about-dialog.ui
Normal file
|
@ -0,0 +1,701 @@
|
|||
<?xml version="1.0"?>
|
||||
<interface>
|
||||
<!-- interface-requires gtk+ 2.12 -->
|
||||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<object class="GtkAboutDialog" id="about_dialog">
|
||||
<property name="visible">True</property>
|
||||
<property name="border_width">5</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<property name="copyright" translatable="yes">Copyright © 2007-2009 René Stadler</property>
|
||||
<property name="comments" translatable="yes">View and analyze GStreamer debug files</property>
|
||||
<property name="license"> GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.</property>
|
||||
<property name="authors">René Stadler <mail@renestadler.de></property>
|
||||
<property name="translator_credits" translatable="yes" comments="TRANSLATORS: Replace this string with your names, one name per line.">translator-credits</property>
|
||||
<property name="logo">gst-debug-viewer.png</property>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkVBox" id="dialog-vbox1">
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkHButtonBox" id="dialog-action_area1"/>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
BIN
debug-viewer/data/gst-debug-viewer.png
Normal file
BIN
debug-viewer/data/gst-debug-viewer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
399
debug-viewer/data/gst-debug-viewer.svg
Normal file
399
debug-viewer/data/gst-debug-viewer.svg
Normal file
|
@ -0,0 +1,399 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://web.resource.org/cc/"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.0"
|
||||
id="Layer_1"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 280.22 69.387"
|
||||
overflow="visible"
|
||||
enable-background="new 0 0 280.22 69.387"
|
||||
xml:space="preserve"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.45"
|
||||
sodipodi:docname="gst-inspector.svg"
|
||||
sodipodi:docbase="/home/cymacs/Desktop"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
inkscape:export-filename="/home/cymacs/Desktop/gst-inspector.png"
|
||||
inkscape:export-xdpi="120"
|
||||
inkscape:export-ydpi="120"
|
||||
sodipodi:modified="TRUE"><metadata
|
||||
id="metadata30"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs28"><linearGradient
|
||||
id="linearGradient2846"><stop
|
||||
id="stop2848"
|
||||
offset="0.0000000"
|
||||
style="stop-color:#8a8a8a;stop-opacity:1.0000000;" /><stop
|
||||
id="stop2850"
|
||||
offset="1.0000000"
|
||||
style="stop-color:#484848;stop-opacity:1.0000000;" /></linearGradient><linearGradient
|
||||
id="linearGradient2366"><stop
|
||||
id="stop2368"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" /><stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.21904762;"
|
||||
offset="0.50000000"
|
||||
id="stop2374" /><stop
|
||||
id="stop2370"
|
||||
offset="1.0000000"
|
||||
style="stop-color:#ffffff;stop-opacity:1.0000000;" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient4477"><stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4479" /><stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop4481" /></linearGradient><linearGradient
|
||||
id="linearGradient4467"><stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4469" /><stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.24761905;"
|
||||
offset="1.0000000"
|
||||
id="stop4471" /></linearGradient><linearGradient
|
||||
id="linearGradient4454"><stop
|
||||
style="stop-color:#729fcf;stop-opacity:0.20784314;"
|
||||
offset="0.0000000"
|
||||
id="stop4456" /><stop
|
||||
style="stop-color:#729fcf;stop-opacity:0.67619050;"
|
||||
offset="1.0000000"
|
||||
id="stop4458" /></linearGradient><linearGradient
|
||||
id="linearGradient4440"><stop
|
||||
style="stop-color:#7d7d7d;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop4442" /><stop
|
||||
id="stop4448"
|
||||
offset="0.50000000"
|
||||
style="stop-color:#b1b1b1;stop-opacity:1.0000000;" /><stop
|
||||
style="stop-color:#686868;stop-opacity:1.0000000;"
|
||||
offset="1.0000000"
|
||||
id="stop4444" /></linearGradient><radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,0.284916,0,30.08928)"
|
||||
r="15.821514"
|
||||
fy="42.07798"
|
||||
fx="24.306795"
|
||||
cy="42.07798"
|
||||
cx="24.306795"
|
||||
id="radialGradient4548"
|
||||
xlink:href="#linearGradient4542"
|
||||
inkscape:collect="always" /><linearGradient
|
||||
id="linearGradient259"><stop
|
||||
style="stop-color:#fafafa;stop-opacity:1.0000000;"
|
||||
offset="0.0000000"
|
||||
id="stop260" /><stop
|
||||
style="stop-color:#bbbbbb;stop-opacity:1.0000000;"
|
||||
offset="1.0000000"
|
||||
id="stop261" /></linearGradient><linearGradient
|
||||
id="linearGradient269"><stop
|
||||
style="stop-color:#a3a3a3;stop-opacity:1.0000000;"
|
||||
offset="0.0000000"
|
||||
id="stop270" /><stop
|
||||
style="stop-color:#4c4c4c;stop-opacity:1.0000000;"
|
||||
offset="1.0000000"
|
||||
id="stop271" /></linearGradient><radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
fy="114.5684"
|
||||
fx="20.892099"
|
||||
r="5.256"
|
||||
cy="114.5684"
|
||||
cx="20.892099"
|
||||
id="aigrd2"><stop
|
||||
id="stop15566"
|
||||
style="stop-color:#F0F0F0"
|
||||
offset="0" /><stop
|
||||
id="stop15568"
|
||||
style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
|
||||
offset="1.0000000" /></radialGradient><radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
fy="64.567902"
|
||||
fx="20.892099"
|
||||
r="5.257"
|
||||
cy="64.567902"
|
||||
cx="20.892099"
|
||||
id="aigrd3"><stop
|
||||
id="stop15573"
|
||||
style="stop-color:#F0F0F0"
|
||||
offset="0" /><stop
|
||||
id="stop15575"
|
||||
style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
|
||||
offset="1.0000000" /></radialGradient><linearGradient
|
||||
id="linearGradient15662"><stop
|
||||
style="stop-color:#ffffff;stop-opacity:1.0000000;"
|
||||
offset="0.0000000"
|
||||
id="stop15664" /><stop
|
||||
style="stop-color:#f8f8f8;stop-opacity:1.0000000;"
|
||||
offset="1.0000000"
|
||||
id="stop15666" /></linearGradient><linearGradient
|
||||
id="linearGradient4542"
|
||||
inkscape:collect="always"><stop
|
||||
id="stop4544"
|
||||
offset="0"
|
||||
style="stop-color:#000000;stop-opacity:1;" /><stop
|
||||
id="stop4546"
|
||||
offset="1"
|
||||
style="stop-color:#000000;stop-opacity:0;" /></linearGradient><linearGradient
|
||||
id="linearGradient5048"><stop
|
||||
id="stop5050"
|
||||
offset="0"
|
||||
style="stop-color:black;stop-opacity:0;" /><stop
|
||||
style="stop-color:black;stop-opacity:1;"
|
||||
offset="0.5"
|
||||
id="stop5056" /><stop
|
||||
id="stop5052"
|
||||
offset="1"
|
||||
style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient
|
||||
id="linearGradient5060"
|
||||
inkscape:collect="always"><stop
|
||||
id="stop5062"
|
||||
offset="0"
|
||||
style="stop-color:black;stop-opacity:1;" /><stop
|
||||
id="stop5064"
|
||||
offset="1"
|
||||
style="stop-color:black;stop-opacity:0;" /></linearGradient><linearGradient
|
||||
id="linearGradient3449"><stop
|
||||
id="stop3451"
|
||||
offset="0.0000000"
|
||||
style="stop-color:#8a8a8a;stop-opacity:1.0000000;" /><stop
|
||||
id="stop3453"
|
||||
offset="1.0000000"
|
||||
style="stop-color:#484848;stop-opacity:1.0000000;" /></linearGradient><linearGradient
|
||||
id="linearGradient3441"><stop
|
||||
id="stop3443"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" /><stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.21904762;"
|
||||
offset="0.50000000"
|
||||
id="stop3445" /><stop
|
||||
id="stop3447"
|
||||
offset="1.0000000"
|
||||
style="stop-color:#ffffff;stop-opacity:1.0000000;" /></linearGradient><linearGradient
|
||||
id="linearGradient3429"><stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3431" /><stop
|
||||
style="stop-color:#ffffff;stop-opacity:0.24761905;"
|
||||
offset="1.0000000"
|
||||
id="stop3433" /></linearGradient><linearGradient
|
||||
id="linearGradient3423"><stop
|
||||
style="stop-color:#729fcf;stop-opacity:0.20784314;"
|
||||
offset="0.0000000"
|
||||
id="stop3425" /><stop
|
||||
style="stop-color:#729fcf;stop-opacity:0.67619050;"
|
||||
offset="1.0000000"
|
||||
id="stop3427" /></linearGradient><linearGradient
|
||||
id="linearGradient3415"><stop
|
||||
style="stop-color:#7d7d7d;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3417" /><stop
|
||||
id="stop3419"
|
||||
offset="0.50000000"
|
||||
style="stop-color:#b1b1b1;stop-opacity:1.0000000;" /><stop
|
||||
style="stop-color:#686868;stop-opacity:1.0000000;"
|
||||
offset="1.0000000"
|
||||
id="stop3421" /></linearGradient><radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,0.284916,0,30.08928)"
|
||||
r="15.821514"
|
||||
fy="42.07798"
|
||||
fx="24.306795"
|
||||
cy="42.07798"
|
||||
cx="24.306795"
|
||||
id="radialGradient3413"
|
||||
xlink:href="#linearGradient4542"
|
||||
inkscape:collect="always" /><linearGradient
|
||||
id="linearGradient3402"><stop
|
||||
style="stop-color:#fafafa;stop-opacity:1.0000000;"
|
||||
offset="0.0000000"
|
||||
id="stop3404" /><stop
|
||||
style="stop-color:#bbbbbb;stop-opacity:1.0000000;"
|
||||
offset="1.0000000"
|
||||
id="stop3406" /></linearGradient><linearGradient
|
||||
id="linearGradient3396"><stop
|
||||
style="stop-color:#a3a3a3;stop-opacity:1.0000000;"
|
||||
offset="0.0000000"
|
||||
id="stop3398" /><stop
|
||||
style="stop-color:#4c4c4c;stop-opacity:1.0000000;"
|
||||
offset="1.0000000"
|
||||
id="stop3400" /></linearGradient><radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
fy="114.5684"
|
||||
fx="20.892099"
|
||||
r="5.256"
|
||||
cy="114.5684"
|
||||
cx="20.892099"
|
||||
id="radialGradient3390"><stop
|
||||
id="stop3392"
|
||||
style="stop-color:#F0F0F0"
|
||||
offset="0" /><stop
|
||||
id="stop3394"
|
||||
style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
|
||||
offset="1.0000000" /></radialGradient><radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
fy="64.567902"
|
||||
fx="20.892099"
|
||||
r="5.257"
|
||||
cy="64.567902"
|
||||
cx="20.892099"
|
||||
id="radialGradient3384"><stop
|
||||
id="stop3386"
|
||||
style="stop-color:#F0F0F0"
|
||||
offset="0" /><stop
|
||||
id="stop3388"
|
||||
style="stop-color:#9a9a9a;stop-opacity:1.0000000;"
|
||||
offset="1.0000000" /></radialGradient><linearGradient
|
||||
id="linearGradient3378"><stop
|
||||
style="stop-color:#ffffff;stop-opacity:1.0000000;"
|
||||
offset="0.0000000"
|
||||
id="stop3380" /><stop
|
||||
style="stop-color:#f8f8f8;stop-opacity:1.0000000;"
|
||||
offset="1.0000000"
|
||||
id="stop3382" /></linearGradient><linearGradient
|
||||
y2="609.50507"
|
||||
x2="302.85715"
|
||||
y1="366.64789"
|
||||
x1="302.85715"
|
||||
gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="linearGradient3370"
|
||||
xlink:href="#linearGradient5048"
|
||||
inkscape:collect="always" /><linearGradient
|
||||
id="linearGradient3362"><stop
|
||||
id="stop3364"
|
||||
offset="0"
|
||||
style="stop-color:black;stop-opacity:0;" /><stop
|
||||
style="stop-color:black;stop-opacity:1;"
|
||||
offset="0.5"
|
||||
id="stop3366" /><stop
|
||||
id="stop3368"
|
||||
offset="1"
|
||||
style="stop-color:black;stop-opacity:0;" /></linearGradient><radialGradient
|
||||
r="117.14286"
|
||||
fy="486.64789"
|
||||
fx="605.71429"
|
||||
cy="486.64789"
|
||||
cx="605.71429"
|
||||
gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient3360"
|
||||
xlink:href="#linearGradient5060"
|
||||
inkscape:collect="always" /><radialGradient
|
||||
r="117.14286"
|
||||
fy="486.64789"
|
||||
fx="605.71429"
|
||||
cy="486.64789"
|
||||
cx="605.71429"
|
||||
gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient3352"
|
||||
xlink:href="#linearGradient5060"
|
||||
inkscape:collect="always" /><radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4477"
|
||||
id="radialGradient3593"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,0.237968,0,28.93278)"
|
||||
cx="24.130018"
|
||||
cy="37.967922"
|
||||
fx="24.130018"
|
||||
fy="37.967922"
|
||||
r="16.528622" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2846"
|
||||
id="linearGradient3595"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="27.366341"
|
||||
y1="26.580296"
|
||||
x2="31.335964"
|
||||
y2="30.557772" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4440"
|
||||
id="linearGradient3597"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.334593,0,0,1.291292,-6.973842,-7.460658)"
|
||||
x1="30.65625"
|
||||
y1="34"
|
||||
x2="33.21875"
|
||||
y2="31.0625" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient2366"
|
||||
id="linearGradient3599"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="18.292673"
|
||||
y1="13.602121"
|
||||
x2="17.500893"
|
||||
y2="25.743469" /><radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4454"
|
||||
id="radialGradient3601"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
cx="18.240929"
|
||||
cy="21.817987"
|
||||
fx="18.240929"
|
||||
fy="21.817987"
|
||||
r="8.3085051" /><radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient4467"
|
||||
id="radialGradient3603"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(2.592963,0,0,2.252104,-25.05975,-18.941)"
|
||||
cx="15.414371"
|
||||
cy="13.078408"
|
||||
fx="15.414371"
|
||||
fy="13.078408"
|
||||
r="6.65625" /></defs><sodipodi:namedview
|
||||
inkscape:cy="22.58868"
|
||||
inkscape:cx="8"
|
||||
inkscape:zoom="8"
|
||||
inkscape:window-height="920"
|
||||
inkscape:window-width="1280"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
guidetolerance="10.0"
|
||||
gridtolerance="10.0"
|
||||
objecttolerance="10.0"
|
||||
borderopacity="1.0"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff"
|
||||
id="base"
|
||||
height="13.546667mm"
|
||||
width="13.546667mm"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="25"
|
||||
inkscape:current-layer="Layer_1"
|
||||
units="mm" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<g
|
||||
id="g3605"
|
||||
transform="matrix(1.0683169,0,0,1.0683169,-4.1837749,-0.3623247)"><path
|
||||
style="fill:#ff3131"
|
||||
id="path21"
|
||||
d="M 146.01171,-12.604976 C 131.30382,-12.604976 109.78446,-24.588273 88.811657,-24.588273 C 67.838848,-24.588273 55.311721,-12.604976 53.130962,-10.970767 C 50.952923,-9.3365572 49.587909,-3.0743532 56.127467,-4.9804782 C 62.667024,-6.8866032 68.110763,-6.8866032 78.462569,-6.8866032 C 88.814376,-6.8866032 113.59944,7.2774532 138.65913,7.2774532 C 163.71882,7.2774532 184.68891,-12.058427 189.31962,-18.869899 C 193.95034,-25.681371 189.59154,-27.85941 186.0512,-26.222482 C 182.50815,-24.588273 159.63194,-12.604976 146.01171,-12.604976 z " /><path
|
||||
style="fill:#319831"
|
||||
id="path23"
|
||||
d="M 209.90631,24.707208 C 194.45338,24.707208 171.84636,12.726631 149.81308,12.726631 C 127.77981,12.726631 114.61639,24.707208 112.32687,26.344137 C 110.03734,27.981065 108.60436,34.24055 115.47293,32.334425 C 122.3415,30.428301 128.06259,30.428301 138.93648,30.428301 C 149.81037,30.428301 175.85167,44.592357 202.17577,44.592357 C 228.49986,44.592357 250.53586,25.256477 255.40042,18.447724 C 260.26498,11.636251 255.68593,9.4554924 251.96613,11.092421 C 248.24634,12.729349 224.21448,24.707208 209.90631,24.707208 z " /><path
|
||||
style="fill:#3232cc"
|
||||
id="path25"
|
||||
d="M 120.98193,65.880587 C 104.95797,65.880587 81.513456,52.93471 58.664434,52.93471 C 35.815411,52.93471 22.162557,65.880587 19.788738,67.645315 C 17.414919,69.410044 15.930264,76.175294 23.051719,74.114172 C 30.175893,72.05578 36.109079,72.05578 47.385397,72.05578 C 58.661714,72.05578 85.668318,87.353714 112.97131,87.353714 C 140.27158,87.353714 163.12061,66.467922 168.16735,59.112623 C 173.21409,51.757318 168.46374,49.405253 164.60526,51.169982 C 160.74679,52.93471 135.82034,65.880587 120.98193,65.880587 z " /></g>
|
||||
</svg>
|
After Width: | Height: | Size: 15 KiB |
88
debug-viewer/data/main-window.ui
Normal file
88
debug-viewer/data/main-window.ui
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?xml version="1.0"?>
|
||||
<interface>
|
||||
<!-- interface-requires gtk+ 2.12 -->
|
||||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<object class="GtkWindow" id="main_window">
|
||||
<property name="title" translatable="yes">GStreamer Debug Viewer</property>
|
||||
<property name="default_width">640</property>
|
||||
<property name="default_height">480</property>
|
||||
<signal name="destroy" handler="handle_main_window_destroy"/>
|
||||
<child>
|
||||
<object class="GtkVBox" id="vbox_main">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkVBox" id="vbox_view">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkVPaned" id="vpaned_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<child>
|
||||
<object class="GtkHBox" id="hbox_view">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="log_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">automatic</property>
|
||||
<property name="vscrollbar_policy">automatic</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="log_view">
|
||||
<property name="name">log_view</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="reorderable">True</property>
|
||||
<property name="rules_hint">True</property>
|
||||
<property name="enable_search">False</property>
|
||||
<property name="fixed_height_mode">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">True</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="line_view_scrolled_window">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">never</property>
|
||||
<property name="vscrollbar_policy">automatic</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="line_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="headers_visible">False</property>
|
||||
<property name="rules_hint">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="resize">False</property>
|
||||
<property name="shrink">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
82
debug-viewer/data/menus.ui
Normal file
82
debug-viewer/data/menus.ui
Normal file
|
@ -0,0 +1,82 @@
|
|||
<!-- -*- mode: xml; -*- -->
|
||||
<ui>
|
||||
<menubar>
|
||||
<menu name="AppMenu" action="AppMenuAction">
|
||||
<menuitem name="AppNewWindow" action="new-window"/>
|
||||
<menuitem name="WindowOpen" action="open-file"/>
|
||||
<menuitem name="WindowReload" action="reload-file"/>
|
||||
<separator/>
|
||||
<menuitem name="ShowAbout" action="show-about"/>
|
||||
<separator/>
|
||||
<menuitem name="WindowClose" action="close-window"/>
|
||||
</menu>
|
||||
<menu name="ViewMenu" action="ViewMenuAction">
|
||||
<menu name="ViewColumnsMenu" action="ViewColumnsMenuAction">
|
||||
<menuitem name="ViewColumnsTime" action="show-time-column"/>
|
||||
<menuitem name="ViewColumnsLevel" action="show-level-column"/>
|
||||
<menuitem name="ViewColumnsPid" action="show-pid-column"/>
|
||||
<menuitem name="ViewColumnsThread" action="show-thread-column"/>
|
||||
<menuitem name="ViewColumnsCode" action="show-code-column"/>
|
||||
<menuitem name="ViewColumnsCategory" action="show-category-column"/>
|
||||
<menuitem name="ViewColumnsFunction" action="show-function-column"/>
|
||||
<menuitem name="ViewColumnsObject" action="show-object-column"/>
|
||||
<menuitem name="ViewColumnsMessage" action="show-message-column"/>
|
||||
</menu>
|
||||
<placeholder name="ViewMenuAdditions"/>
|
||||
<separator/>
|
||||
<menuitem name="ViewContextMenuHideLevel" action="hide-log-level"/>
|
||||
<menuitem name="ViewContextMenuHideLevelAndAbove" action="hide-log-level-and-above"/>
|
||||
<menuitem name="ViewContextMenuShowOnlyLevel" action="show-only-log-level"/>
|
||||
<menuitem name="ViewContextMenuHideCategory" action="hide-log-category"/>
|
||||
<menuitem name="ViewContextMenuShowOnlyCategory" action="show-only-log-category"/>
|
||||
<menuitem name="ViewContextMenuHideThread" action="hide-thread"/>
|
||||
<menuitem name="ViewContextMenuShowOnlyThread" action="show-only-thread"/>
|
||||
<menuitem name="ViewContextMenuHideObject" action="hide-object"/>
|
||||
<menuitem name="ViewContextMenuShowOnlyObject" action="show-only-object"/>
|
||||
<menuitem name="ViewContextMenuHideFunction" action="hide-function"/>
|
||||
<menuitem name="ViewContextMenuShowOnlyFunction" action="show-only-function"/>
|
||||
<menuitem name="ViewContextMenuHideFilename" action="hide-filename"/>
|
||||
<menuitem name="ViewContextMenuShowOnlyFilename" action="show-only-filename"/>
|
||||
<menuitem name="ViewContextMenuHideBefore" action="hide-before-line"/>
|
||||
<menuitem name="ViewContextMenuHideAfter" action="hide-after-line"/>
|
||||
<menuitem name="ViewContextMenuShowHidden" action="show-hidden-lines"/>
|
||||
<separator/>
|
||||
<menuitem name="ViewContextMenuCopyMessage" action="edit-copy-message"/>
|
||||
<menuitem name="ViewContextMenuCopyLine" action="edit-copy-line"/>
|
||||
<separator/>
|
||||
<menuitem name="ZoomIn" action="enlarge-text"/>
|
||||
<menuitem name="ZoomOut" action="shrink-text"/>
|
||||
<menuitem name="Zoom100" action="reset-text"/>
|
||||
</menu>
|
||||
</menubar>
|
||||
<menubar name="context">
|
||||
<menu name="LogViewContextMenu" action="ViewMenuAction">
|
||||
<placeholder name="LogViewContextMenuAdditions"/>
|
||||
<separator/>
|
||||
<menuitem name="ViewContextMenuSetBaseTime" action="set-base-time"/>
|
||||
<menuitem name="ViewContextMenuHideLevel" action="hide-log-level"/>
|
||||
<menuitem name="ViewContextMenuHideLevelAndAbove" action="hide-log-level-and-above"/>
|
||||
<menuitem name="ViewContextMenuShowOnlyLevel" action="show-only-log-level"/>
|
||||
<menuitem name="ViewContextMenuHideCategory" action="hide-log-category"/>
|
||||
<menuitem name="ViewContextMenuShowOnlyCategory" action="show-only-log-category"/>
|
||||
<menuitem name="ViewContextMenuHideThread" action="hide-thread"/>
|
||||
<menuitem name="ViewContextMenuShowOnlyThread" action="show-only-thread"/>
|
||||
<menuitem name="ViewContextMenuHideObject" action="hide-object"/>
|
||||
<menuitem name="ViewContextMenuShowOnlyObject" action="show-only-object"/>
|
||||
<menuitem name="ViewContextMenuHideFunction" action="hide-function"/>
|
||||
<menuitem name="ViewContextMenuShowOnlyFunction" action="show-only-function"/>
|
||||
<menuitem name="ViewContextMenuHideFilename" action="hide-filename"/>
|
||||
<menuitem name="ViewContextMenuShowOnlyFilename" action="show-only-filename"/>
|
||||
<menuitem name="ViewContextMenuHideBefore" action="hide-before-line"/>
|
||||
<menuitem name="ViewContextMenuHideAfter" action="hide-after-line"/>
|
||||
<menuitem name="ViewContextMenuShowHidden" action="show-hidden-lines"/>
|
||||
<separator/>
|
||||
<menuitem name="ViewContextMenuCopyMessage" action="edit-copy-message"/>
|
||||
<menuitem name="ViewContextMenuCopyLine" action="edit-copy-line"/>
|
||||
</menu>
|
||||
<menu name="LineViewContextMenu" action="LineViewContextMenuAction">
|
||||
<menuitem name="LineViewContextMenuClear" action="clear-line-view"/>
|
||||
<placeholder name="LineViewContextMenuAdditions"/>
|
||||
</menu>
|
||||
</menubar>
|
||||
</ui>
|
6
debug-viewer/data/meson.build
Normal file
6
debug-viewer/data/meson.build
Normal file
|
@ -0,0 +1,6 @@
|
|||
install_data('about-dialog.ui', 'main-window.ui', 'menus.ui', 'gst-debug-viewer.png',
|
||||
install_dir: join_paths(get_option('datadir'), 'gst-debug-viewer'))
|
||||
install_data('gst-debug-viewer.png',
|
||||
install_dir: join_paths(get_option('datadir'), 'icons/hicolor/48x48/apps'))
|
||||
install_data('gst-debug-viewer.svg',
|
||||
install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps'))
|
70
debug-viewer/gst-debug-viewer
Executable file
70
debug-viewer/gst-debug-viewer
Executable file
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8; mode: python; -*-
|
||||
#
|
||||
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
||||
#
|
||||
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""GStreamer Debug Viewer program invocation."""
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
|
||||
def substituted(s):
|
||||
if s.startswith("@") and s.endswith("@"):
|
||||
return None
|
||||
else:
|
||||
return s
|
||||
|
||||
# These "$"-enclosed strings are substituted at install time by a custom
|
||||
# distutils extension (see setup.py). If you don't see any dollar signs at
|
||||
# all, you are looking at an installed version of this file.
|
||||
data_dir = substituted("@DATADIR@")
|
||||
lib_dir = substituted("@LIBDIR@")
|
||||
|
||||
if data_dir:
|
||||
installed = True
|
||||
else:
|
||||
# Substitution has not been run, we are running uninstalled:
|
||||
lib_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
|
||||
installed = False
|
||||
|
||||
if lib_dir:
|
||||
if not os.path.normpath(lib_dir) in [os.path.normpath(p)
|
||||
for p in sys.path]:
|
||||
sys.path.insert(0, lib_dir)
|
||||
|
||||
try:
|
||||
import GstDebugViewer
|
||||
except ImportError as exc:
|
||||
print(str(exc), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
if installed:
|
||||
GstDebugViewer.Paths.setup_installed(data_dir)
|
||||
else:
|
||||
# Assume that we reside inside the source dist.
|
||||
source_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
|
||||
GstDebugViewer.Paths.setup_uninstalled(source_dir)
|
||||
|
||||
GstDebugViewer.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
85
debug-viewer/meson.build
Normal file
85
debug-viewer/meson.build
Normal file
|
@ -0,0 +1,85 @@
|
|||
python3.install_sources (
|
||||
'GstDebugViewer/Main.py',
|
||||
'GstDebugViewer/Data.py',
|
||||
subdir: 'GstDebugViewer')
|
||||
|
||||
python3.install_sources (
|
||||
'GstDebugViewer/GUI/columns.py',
|
||||
'GstDebugViewer/GUI/__init__.py',
|
||||
'GstDebugViewer/GUI/models.py',
|
||||
'GstDebugViewer/GUI/filters.py',
|
||||
'GstDebugViewer/GUI/colors.py',
|
||||
'GstDebugViewer/GUI/window.py',
|
||||
'GstDebugViewer/GUI/app.py',
|
||||
subdir: 'GstDebugViewer/GUI')
|
||||
|
||||
python3.install_sources (
|
||||
'GstDebugViewer/Plugins/__init__.py',
|
||||
'GstDebugViewer/Plugins/FindBar.py',
|
||||
'GstDebugViewer/Plugins/Timeline.py',
|
||||
subdir: 'GstDebugViewer/Plugins')
|
||||
|
||||
python3.install_sources (
|
||||
'GstDebugViewer/Common/Main.py',
|
||||
'GstDebugViewer/Common/utils.py',
|
||||
'GstDebugViewer/Common/__init__.py',
|
||||
'GstDebugViewer/Common/generictreemodel.py',
|
||||
'GstDebugViewer/Common/Data.py',
|
||||
'GstDebugViewer/Common/GUI.py',
|
||||
subdir: 'GstDebugViewer/Common')
|
||||
|
||||
if find_program('msgfmt', required : get_option('nls')).found()
|
||||
# Desktop launcher and description file.
|
||||
desktop_file = i18n.merge_file(
|
||||
input: 'org.freedesktop.GstDebugViewer.desktop.in',
|
||||
output: 'org.freedesktop.GstDebugViewer.desktop',
|
||||
type: 'desktop',
|
||||
po_dir: 'po',
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('datadir'), 'applications'),
|
||||
)
|
||||
|
||||
# Appdata file.
|
||||
appdata_file = i18n.merge_file(
|
||||
input: 'org.freedesktop.GstDebugViewer.appdata.xml.in',
|
||||
output: 'org.freedesktop.GstDebugViewer.appdata.xml',
|
||||
po_dir: 'po',
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('datadir'), 'metainfo'),
|
||||
)
|
||||
else
|
||||
install_data('org.freedesktop.GstDebugViewer.desktop.in',
|
||||
rename: 'org.freedesktop.GstDebugViewer.desktop',
|
||||
install_dir: join_paths(get_option('datadir'), 'applications'))
|
||||
install_data('org.freedesktop.GstDebugViewer.appdata.xml.in',
|
||||
rename: 'org.freedesktop.GstDebugViewer.appdata.xml',
|
||||
install_dir: join_paths(get_option('datadir'), 'metainfo'))
|
||||
endif
|
||||
|
||||
cdata = configuration_data()
|
||||
cdata.set('LIBDIR', join_paths(get_option('prefix'), get_option('libdir')))
|
||||
cdata.set('DATADIR', join_paths(get_option('prefix'), get_option('datadir')))
|
||||
cdata.set('VERSION', meson.project_version())
|
||||
|
||||
configure_file(input: 'gst-debug-viewer',
|
||||
output: 'gst-debug-viewer',
|
||||
configuration: cdata,
|
||||
install_dir: get_option('bindir'))
|
||||
|
||||
init_file = configure_file(
|
||||
input: 'GstDebugViewer/__init__.py',
|
||||
output: '__init__.py',
|
||||
configuration: cdata)
|
||||
python3.install_sources (init_file, subdir: 'GstDebugViewer')
|
||||
|
||||
pkgdatadir = join_paths(get_option('datadir'), meson.project_name())
|
||||
icondir = join_paths(get_option('datadir'), 'icons/hicolor')
|
||||
|
||||
subdir('data')
|
||||
|
||||
|
||||
if run_command(python3,
|
||||
'-c', 'import gi; gi.require_version("Gtk", "3.0")').returncode() == 0
|
||||
test('gst-debug-viewer', python3, args: ['-m', 'unittest'],
|
||||
workdir: meson.current_source_dir())
|
||||
endif
|
29
debug-viewer/org.freedesktop.GstDebugViewer.appdata.xml.in
Normal file
29
debug-viewer/org.freedesktop.GstDebugViewer.appdata.xml.in
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>org.freedesktop.GstDebugViewer</id>
|
||||
<launchable type="desktop-id">org.freedesktop.GstDebugViewer.desktop</launchable>
|
||||
<metadata_license>CC-BY-3.0</metadata_license>
|
||||
<project_license>GPL-3.0+</project_license>
|
||||
<name>GStreamer Debug Viewer</name>
|
||||
<summary>Examine GStreamer debug log information</summary>
|
||||
<description>
|
||||
<p>View and read GStreamer debug logs in an efficient way</p>
|
||||
</description>
|
||||
<url type="homepage">https://gstreamer.freedesktop.org/</url>
|
||||
<url type="bugtracker">https://gitlab.freedesktop.org/gstreamer/gst-devtools/issues/</url>
|
||||
<update_contact>tsaunier@gnome.org</update_contact>
|
||||
<project_group>GStreamer</project_group>
|
||||
<translation type="gettext">GStreamer</translation>
|
||||
<developer_name>The GStreamer Team</developer_name>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>The main window</caption>
|
||||
<image>https://gitlab.freedesktop.org/gstreamer/gst-devtools/-/raw/master/debug-viewer/screenshots/gst-debug-viewer.png?inline=false</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.0" />
|
||||
<releases>
|
||||
<release version="1.16.2" date="2019-12-03" />
|
||||
<release version="1.16.1" date="2019-09-23" />
|
||||
</releases>
|
||||
</component>
|
9
debug-viewer/org.freedesktop.GstDebugViewer.desktop.in
Normal file
9
debug-viewer/org.freedesktop.GstDebugViewer.desktop.in
Normal file
|
@ -0,0 +1,9 @@
|
|||
[Desktop Entry]
|
||||
Name=GStreamer Debug Viewer
|
||||
Comment=Examine GStreamer debug log information
|
||||
StartupNotify=true
|
||||
Exec=gst-debug-viewer
|
||||
Icon=gst-debug-viewer
|
||||
Type=Application
|
||||
Categories=GNOME;Development
|
||||
|
BIN
debug-viewer/pixmaps/gst-debug-viewer.png
Normal file
BIN
debug-viewer/pixmaps/gst-debug-viewer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2 KiB |
0
debug-viewer/po/LINGUAS
Normal file
0
debug-viewer/po/LINGUAS
Normal file
0
debug-viewer/po/POTFILES.in
Normal file
0
debug-viewer/po/POTFILES.in
Normal file
BIN
debug-viewer/screenshots/gst-debug-viewer.png
Normal file
BIN
debug-viewer/screenshots/gst-debug-viewer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 175 KiB |
6
docs/api.md
Normal file
6
docs/api.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# GstValidate API reference
|
||||
|
||||
This is GstValidate API reference but note that the GstValidate is not
|
||||
totally stable and might very well change even between minor versions.
|
||||
|
||||
The override API should be mostly stable still.
|
8
docs/fakesrc.simple.validatetest
Normal file
8
docs/fakesrc.simple.validatetest
Normal file
|
@ -0,0 +1,8 @@
|
|||
meta,
|
||||
args = {
|
||||
"fakesrc num-buffers=1 ! fakesink name=sink",
|
||||
},
|
||||
configs = {
|
||||
"$(validateflow), pad=sink:sink, buffers-checksum=true",
|
||||
}
|
||||
# The validate tool will simply play the pipeline until EOS is reached.
|
1
docs/fakesrc.simple.validatetest.ini
Symbolic link
1
docs/fakesrc.simple.validatetest.ini
Symbolic link
|
@ -0,0 +1 @@
|
|||
fakesrc.simple.validatetest
|
|
@ -0,0 +1,4 @@
|
|||
event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1;
|
||||
event segment: format=BYTES, start=0, offset=0, stop=18446744073709551615, time=0, base=0, position=0
|
||||
buffer: checksum=da39a3ee5e6b4b0d3255bfef95601890afd80709, dts=0:00:00.000000000, flags=discont
|
||||
event eos: (no structure)
|
1040
docs/ges-validate-action-types.md
Normal file
1040
docs/ges-validate-action-types.md
Normal file
File diff suppressed because it is too large
Load diff
1
docs/gi-index.md
Normal file
1
docs/gi-index.md
Normal file
|
@ -0,0 +1 @@
|
|||
# GstValidate API reference
|
1531
docs/gst-validate-action-types.md
Normal file
1531
docs/gst-validate-action-types.md
Normal file
File diff suppressed because it is too large
Load diff
110
docs/gst-validate-config.md
Normal file
110
docs/gst-validate-config.md
Normal file
|
@ -0,0 +1,110 @@
|
|||
---
|
||||
title: Configuration
|
||||
short-description: GstValidate configuration
|
||||
...
|
||||
|
||||
# GstValidate Configuration
|
||||
|
||||
GstValidate comes with some possible configuration files
|
||||
to setup its plugins and core behaviour. The config format is very similar
|
||||
to the [scenario](gst-validate-scenarios.md) file format.
|
||||
|
||||
You can check the [ssim plugin](plugins/ssim.md)
|
||||
and the [validate flow plugin](gst-validate-flow.md)
|
||||
for examples.
|
||||
|
||||
## Core settings parameters
|
||||
|
||||
Config name should be `core`.
|
||||
|
||||
### `verbosity`
|
||||
|
||||
Default: `position`
|
||||
|
||||
See [GstValidateVerbosityFlags](GstValidateVerbosityFlags) for possible values.
|
||||
|
||||
### `action`
|
||||
|
||||
The [action type](gst-validate-action-types.md) to execute, the action type
|
||||
must be a CONFIG action or the action type must have a `as-config` argument. When the `action`
|
||||
is specified in a parameter, a validate action is executed using the other parameters of the
|
||||
config as configuration for the validate scenario action.
|
||||
|
||||
#### Example:
|
||||
|
||||
```
|
||||
GST_VALIDATE_CONFIG="core, action=set-property, target-element-name="videotestsrc0", property-name=pattern, property-value=blue" gst-validate-1.0 videotestsrc ! autovideosink
|
||||
```
|
||||
|
||||
This will execute the `set-property, target-element-name="videotestsrc0",
|
||||
property-name=pattern, property-value=blue` validate action directly from the
|
||||
config file
|
||||
|
||||
### `scenario-action-execution-interval`
|
||||
|
||||
Default: `0` meaning that action are executed in `idle` callbacks.
|
||||
|
||||
Set the interval between [GstValidateScenario](gst-validate-scenarios.md) actions execution.
|
||||
|
||||
### `max-latency`
|
||||
|
||||
Default: `GST_CLOCK_TIME_NONE` - disabled
|
||||
|
||||
Set the maximum latency reported by the pipeline, over that defined latency the scenario will report
|
||||
an `config::latency-too-high` issue.
|
||||
|
||||
### `max-dropped`
|
||||
|
||||
Default: `GST_CLOCK_TIME_NONE` - disabled
|
||||
|
||||
The maximum number of dropped buffers, a `config::too-many-buffers-dropped` issue will be reported
|
||||
if that limit is reached.
|
||||
|
||||
### `fail-on-missing-plugin`
|
||||
|
||||
Default: `false` meaning that tests are marked as skipped when a GStreamer plugin is missing.
|
||||
|
||||
## Variables
|
||||
|
||||
You can use variables in the configs the same way you can set them in
|
||||
[gst-validate-scenarios](gst-validate-scenarios.md).
|
||||
|
||||
Defaults variables are:
|
||||
|
||||
- `$(TMPDIR)`: The default temporary directory as returned by `g_get_tmp_dir`.
|
||||
- `$(CONFIG_PATH)`: The path of the running scenario.
|
||||
- `$(CONFIG_DIR)`: The directory the running scenario is in.
|
||||
- `$(CONFIG_NAME)`: The name of the config file
|
||||
- `$(LOGSDIR)`: The directory where to place log files. This uses the
|
||||
`GST_VALIDATE_LOGSDIR` environment variable if available or `$(TMPDIR)` if
|
||||
the variables hasn't been set. (Note that the
|
||||
[gst-validate-launcher](gst-validate-launcher.md) set the environment
|
||||
variables).
|
||||
|
||||
You can also set you own variables by using the `set-vars=true` argument:
|
||||
|
||||
``` yaml
|
||||
core, set-vars=true, log-path=$(CONFIG_DIR/../log)
|
||||
```
|
||||
|
||||
It is also possible to set global variables (also usable from
|
||||
[scenarios](gst-validate-scenarios.md)) with:
|
||||
|
||||
``` yaml
|
||||
set-globals, TESTSUITE_ROOT_DIR=$(CONFIG_DIR)
|
||||
```
|
||||
|
||||
## `change-issue-severity` settings parameters
|
||||
|
||||
You can change issues severity with the `change-issue-severity` configuration
|
||||
with the following parameters:
|
||||
|
||||
* `issue-id`: The GQuark name of the issue, for example: `event::segment-has-wrong-start`,
|
||||
You can use `gst-validate-1.0 --print-issue-types` to list all issue types.
|
||||
* `new-severity`: The new [`severity`](GstValidateReportLevel) of the issue
|
||||
* `element-name` (*optional*): The name of the element the severity
|
||||
change applies to
|
||||
* `element-factory-name` (*optional*): The element factory name of the elements the
|
||||
severity change applies to
|
||||
* `element-classification` (*optional*): The classification of the elements the
|
||||
severity change applies to
|
153
docs/gst-validate-environment-variables.md
Normal file
153
docs/gst-validate-environment-variables.md
Normal file
|
@ -0,0 +1,153 @@
|
|||
---
|
||||
title: Environment variables
|
||||
short-description: Environment variables influencing runtime behaviour
|
||||
...
|
||||
|
||||
# GstValidate Environment Variables
|
||||
|
||||
The runtime behaviour of GstValidate applications can be influenced by a
|
||||
number of environment variables.
|
||||
|
||||
**GST_VALIDATE.**
|
||||
|
||||
This environment variable can be set to a list of debug options, which
|
||||
cause GstValidate to print out different types of test result
|
||||
information and consider differently the level of the reported issues.
|
||||
|
||||
* `fatal-criticals`: Causes GstValidate to consider only critical issues as import enough
|
||||
to consider the test failed (default behaviour)
|
||||
* `fatal-warnings`: Causes GstValidate to consider warning, and critical issues as
|
||||
import enough to consider the test failed
|
||||
* `fatal-issues`: Causes GstValidate to consider issue, warning, and critical issues
|
||||
as import enough to consider the test failed
|
||||
* `print-issues`: Causes GstValidate to print issue, warning and critical issues in
|
||||
the final reports (default behaviour)
|
||||
* `print-warnings`: Causes GstValidate to only print warning and critical issues in the
|
||||
final reports
|
||||
* `print-criticals`: Causes GstValidate to only print critical issues in the final
|
||||
reports
|
||||
|
||||
**GST_VALIDATE_FILE.**
|
||||
|
||||
Set this variable to a colon-separated list of paths to redirect all
|
||||
GstValidate messages to this file. If left unset, debug messages will be
|
||||
outputed into the standard error.
|
||||
|
||||
You can use the special names `stdout` and `stderr` to use those output.
|
||||
|
||||
**GST_VALIDATE_SCENARIOS_PATH.**
|
||||
|
||||
Set this variable to a colon-separated list of paths. GstValidate will
|
||||
scan these paths for GstValidate scenario files. By default GstValidate
|
||||
will look for scenarios in the user data directory as specified in the
|
||||
[XDG standard]:
|
||||
`.local/share/gstreamer-GST_API_VERSION/validate/scenarios` and the
|
||||
system wide user data directory:
|
||||
`/usr/lib/gstreamer-GST_API_VERSION/validate/scenarios`
|
||||
|
||||
**GST_VALIDATE_CONFIG.**
|
||||
|
||||
Set this variable to a colon-separated list of paths to GstValidate
|
||||
config files or directly as a string in the GstCaps serialization
|
||||
format. The config file has a format similar to the scenario file. The
|
||||
name of the configuration corresponds to the name of the plugin the
|
||||
configuration applies to.
|
||||
|
||||
The special name "core" is used to configure GstValidate core
|
||||
functionalities (monitors, scenarios, etc...).
|
||||
|
||||
If you want to make sure to set a property on a element of a type (for
|
||||
example to disable QoS on all sinks) you can do:
|
||||
|
||||
```
|
||||
core, action=set-property, target-element-klass=Sink
|
||||
```
|
||||
|
||||
If you want the GstPipeline to get dumped when an issue of a certain
|
||||
level (and higher) happens, you can do:
|
||||
|
||||
```
|
||||
core, action=dot-pipeline, report-level=issue
|
||||
```
|
||||
|
||||
Note that you will still need to set GST_DEBUG_DUMP_DOT_DIR.
|
||||
|
||||
For more examples you can look at the ssim GstValidate plugin
|
||||
documentation to see how to configure that plugin.
|
||||
|
||||
You can also check that a src pad is pushing buffers at a minimum
|
||||
frequency. For example to check if v4l2src is producing at least 60 frames
|
||||
per second you can do:
|
||||
|
||||
``` yaml
|
||||
core,min-buffer-frequency=60,target-element-factory-name=v4l2src
|
||||
```
|
||||
|
||||
This config accepts the following fields:
|
||||
- `min-buffer-frequency`: the expected minimum rate, in buffers per
|
||||
second, at which buffers are pushed on the pad
|
||||
|
||||
- `target-element-{factory-name,name,klass}`: the factory-name, object
|
||||
name or class of the element to check
|
||||
|
||||
- `name`: (optional) only check the frequency if the src pad has this
|
||||
name
|
||||
|
||||
- `buffer-frequency-start`: (optional) if defined, validate will
|
||||
ignore the frequency of the pad during the time specified in this
|
||||
field, in ns. This can be useful when testing live pipelines where
|
||||
configuring and setting up elements can take some time slowing down
|
||||
the first buffers until the pipeline reaches its cruising speed.
|
||||
**GST_VALIDATE_OVERRIDE.**
|
||||
|
||||
Set this variable to a colon-separated list of dynamically linkable
|
||||
files that GstValidate will scan looking for overrides. By default
|
||||
GstValidate will look for scenarios in the user data directory as
|
||||
specified in the [XDG standard]:
|
||||
`.local/share/gstreamer-GST_API_VERSION/validate/scenarios` and the
|
||||
system wide user data directory:
|
||||
`/usr/lib/gstreamer-GST_API_VERSION/validate/scenarios`
|
||||
|
||||
**GST_VALIDATE_SCENARIO_WAIT_MULITPLIER.**
|
||||
|
||||
A decimal number to set as a multiplier for the wait actions. For
|
||||
example if you set `GST_VALIDATE_SCENARIO_WAIT_MULITPLIER=0.5`, for a
|
||||
wait action that has a duration of 2.0 the waiting time will only be of
|
||||
1.0 second. If set to 0, wait action will be ignored.
|
||||
|
||||
**GST_VALIDATE_REPORTING_DETAILS.**
|
||||
|
||||
The reporting level can be set through the
|
||||
GST_VALIDATE_REPORTING_DETAILS environment variable, as a
|
||||
comma-separated list of (optional) object categories / names and levels.
|
||||
Omit the object category / name to set the global level.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
GST_VALIDATE_REPORTING_DETAILS=synthetic,h264parse:all
|
||||
GST_VALIDATE_REPORTING_DETAILS=none,h264parse::sink_0:synthetic
|
||||
```
|
||||
|
||||
Levels being:
|
||||
|
||||
* `none`: No debugging level specified or desired. Used to deactivate
|
||||
debugging output.
|
||||
* `synthetic`: Summary of the issues found, with no details.
|
||||
* `subchain`: If set as the default level, similar issues can be reported multiple
|
||||
times for different subchains. If set as the level for a particular
|
||||
object (`my_object:subchain`), validate will report the issues where
|
||||
the object is the first to report an issue for a subchain.
|
||||
* `monitor`: If set as the default level, all the distinct issues for all the
|
||||
monitors will be reported. If set as the level for a particular
|
||||
object, all the distinct issues for this object will be reported.
|
||||
Note that if the same issue happens twice on the same object, up
|
||||
until this level that issue is only reported once.
|
||||
* `all`: All the issues will be reported, even those that repeat themselves
|
||||
inside the same object. This can be **very** verbose if set
|
||||
globally.
|
||||
|
||||
Setting the reporting level allows to control the way issues are
|
||||
reported when calling [gst_validate_runner_printf()](gst_validate_runner_printf).
|
||||
|
||||
[XDG standard]: http://www.freedesktop.org/wiki/Software/xdg-user-dirs/
|
196
docs/gst-validate-flow.md
Normal file
196
docs/gst-validate-flow.md
Normal file
|
@ -0,0 +1,196 @@
|
|||
---
|
||||
title: Validate Flow
|
||||
short-description: Validate a pad data flow
|
||||
...
|
||||
|
||||
# Validate Flow
|
||||
|
||||
Validate Flow — GStreamer validate component to record a log of buffers and
|
||||
events flowing in a specified pad and compare it with an expectation file.
|
||||
|
||||
## Description
|
||||
|
||||
This component exists for the purpose of testing non-regular-playback use cases
|
||||
where the test author specifies the full pipeline, a series of actions and needs
|
||||
to check whether the generated buffers and events make sense.
|
||||
|
||||
The testing procedure goes like this:
|
||||
|
||||
1. The test author writes a [.validatetest](gst-validate-test-file.md) test
|
||||
where validateflow is used. A pad where monitoring will occur is specified
|
||||
and possibly a list of [actions](gst-validate-action-types.md) to run can
|
||||
also be specified.
|
||||
|
||||
2. The test author runs the test with the desired pipeline, the configuration
|
||||
and the actions. Since an expectation file does not exist at
|
||||
this point, validateflow will create one. The author should check its
|
||||
contents for any missing or unwanted events. No actual checking is done by
|
||||
validateflow in this step, since there is nothing to compare to yet.
|
||||
|
||||
3. Further executions of the test will also record the produced buffers and
|
||||
events, but now they will be compared to the previous log (expectation file).
|
||||
Any difference will be reported as a test failure. The original expectation
|
||||
file is never modified by validateflow. Any desired changes can be made by
|
||||
editing the file manually or deleting it and running the test again.
|
||||
|
||||
## Example
|
||||
|
||||
### Simplest example
|
||||
|
||||
The following is an example of a `fakesrc.simple.validatetest` file using
|
||||
validateflow.
|
||||
|
||||
{{ fakesrc.simple.validatetest.ini }}
|
||||
|
||||
Then generate the expectation file with:
|
||||
|
||||
``` bash
|
||||
gst-validate-1.0 --set-test-file /path/to/fakesrc.simple.validatetest
|
||||
```
|
||||
|
||||
This will generate the
|
||||
`/path/to/fakesrc.simple/flow-expectations/log-sink-sink-expected` file
|
||||
containing:
|
||||
|
||||
{{ plugins/fakesrc.simple/flow-expectations/log-sink-sink-expected.log }}
|
||||
|
||||
Note that the test will be marked as "SKIPPED" when we generate expectation
|
||||
files.
|
||||
|
||||
The test can now be run with:
|
||||
|
||||
```
|
||||
gst-validate-1.0 --set-test-file /path/to/fakesrc.simple.validatetest
|
||||
```
|
||||
|
||||
### Example controlling the source
|
||||
|
||||
The following is an example of the `qtdemux_change_edit_list.validatetest` file using validateflow.
|
||||
|
||||
``` ini
|
||||
set-globals, media_dir="$(test_dir)/../../../medias/"
|
||||
meta,
|
||||
seek=false,
|
||||
handles-states=false,
|
||||
args = {
|
||||
"appsrc ! qtdemux ! fakesink async=false",
|
||||
},
|
||||
configs = {
|
||||
"$(validateflow), pad=fakesink0:sink, record-buffers=false",
|
||||
}
|
||||
|
||||
# Scenario action types
|
||||
appsrc-push, target-element-name=appsrc0, file-name="$(media_dir)/fragments/car-20120827-85.mp4/init.mp4"
|
||||
appsrc-push, target-element-name=appsrc0, file-name="$(media_dir)/fragments/car-20120827-85.mp4/media1.mp4"
|
||||
checkpoint, text="A moov with a different edit list is now pushed"
|
||||
appsrc-push, target-element-name=appsrc0, file-name="$(media_dir)/fragments/car-20120827-86.mp4/init.mp4"
|
||||
appsrc-push, target-element-name=appsrc0, file-name="$(media_dir)/fragments/car-20120827-86.mp4/media2.mp4"
|
||||
stop
|
||||
```
|
||||
|
||||
This example shows the elements of a typical validate flow test (a pipeline, a
|
||||
config and a scenario). Some actions typically used together with validateflow
|
||||
can also be seen. Notice variable interpolation is used to fill absolute paths
|
||||
for media files in the scenario (`$(test_dir)`). In the configuration,
|
||||
`$(validateflow)` is expanded to something like this, containing proper paths
|
||||
for expectations and actual results (these values are interpolated from the
|
||||
`.validatetest` file location):
|
||||
|
||||
``` ini
|
||||
validateflow, expectations-dir="/validate/test/file/path/validateqtdemux_change_edit_list/flow-expectations/", actual-results-dir="$(GST_VALIDATE_LOGSDIR)/logs/validate/launch_pipeline/qtdemux_change_edit_list"
|
||||
```
|
||||
|
||||
The resulting log looks like this:
|
||||
|
||||
``` ini
|
||||
event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1;
|
||||
event caps: video/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)2.1, profile=(string)main, codec_data=(buffer)014d4015ffe10016674d4015d901b1fe4e1000003e90000bb800f162e48001000468eb8f20, width=(int)426, height=(int)240, pixel-aspect-ratio=(fraction)1/1;
|
||||
event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=none, time=0:00:00.000000000, base=0:00:00.000000000, position=0:00:00.000000000
|
||||
event tag: GstTagList-stream, taglist=(taglist)"taglist\,\ video-codec\=\(string\)\"H.264\\\ /\\\ AVC\"\;";
|
||||
event tag: GstTagList-global, taglist=(taglist)"taglist\,\ datetime\=\(datetime\)2012-08-27T01:00:50Z\,\ container-format\=\(string\)\"ISO\\\ fMP4\"\;";
|
||||
event tag: GstTagList-stream, taglist=(taglist)"taglist\,\ video-codec\=\(string\)\"H.264\\\ /\\\ AVC\"\;";
|
||||
event caps: video/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)2.1, profile=(string)main, codec_data=(buffer)014d4015ffe10016674d4015d901b1fe4e1000003e90000bb800f162e48001000468eb8f20, width=(int)426, height=(int)240, pixel-aspect-ratio=(fraction)1/1, framerate=(fraction)24000/1001;
|
||||
|
||||
CHECKPOINT: A moov with a different edit list is now pushed
|
||||
|
||||
event caps: video/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)3, profile=(string)main, codec_data=(buffer)014d401effe10016674d401ee8805017fcb0800001f480005dc0078b168901000468ebaf20, width=(int)640, height=(int)360, pixel-aspect-ratio=(fraction)1/1;
|
||||
event segment: format=TIME, start=0:00:00.041711111, offset=0:00:00.000000000, stop=none, time=0:00:00.000000000, base=0:00:00.000000000, position=0:00:00.041711111
|
||||
event tag: GstTagList-stream, taglist=(taglist)"taglist\,\ video-codec\=\(string\)\"H.264\\\ /\\\ AVC\"\;";
|
||||
event tag: GstTagList-stream, taglist=(taglist)"taglist\,\ video-codec\=\(string\)\"H.264\\\ /\\\ AVC\"\;";
|
||||
event caps: video/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)3, profile=(string)main, codec_data=(buffer)014d401effe10016674d401ee8805017fcb0800001f480005dc0078b168901000468ebaf20, width=(int)640, height=(int)360, pixel-aspect-ratio=(fraction)1/1, framerate=(fraction)24000/1001;
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
In order to use the plugin a validate configuration must be provided,
|
||||
containing a line starting by `validateflow` followed by a number of settings.
|
||||
Every `validateflow` line creates a `ValidateFlowOverride`, which listens to a
|
||||
given pad. A test may have several `validateflow` lines, therefore having
|
||||
several overrides and listening to different pads with different settings.
|
||||
|
||||
* `pad`: Required. Name of the pad that will be monitored.
|
||||
* `record-buffers`: Default: false. Whether buffers will be logged. By default
|
||||
only events are logged.
|
||||
* `buffers-checksum`: Default: 'none'. Define the type of checksums to be used
|
||||
valid values are:
|
||||
* `none`: No checksum recorded
|
||||
* `as-id`: Record checksum as 'ids' where the IDs are incremented on each new
|
||||
checksum passed in
|
||||
* `md5`: md5 checksum
|
||||
* `sha1`: sha1 checksum
|
||||
* `sha256`: sha256 checksum
|
||||
* `sha512`: sha512 checksum
|
||||
* *Note*: for backward compatibility reasons, this can be passed as a
|
||||
boolean and it will default to 'sha1' if true, 'none' if false.
|
||||
* `ignored-fields`: Default: `"stream-start={ stream-id }"` (as they are often
|
||||
non reproducible). Key with a serialized GstValueList(str) of fields to not
|
||||
record.
|
||||
* `logged-fields`: Default: `NULL` Key with a serialized GstValueList(str) of
|
||||
fields to record, eg. `logged-event-fields="stream-start={flags},
|
||||
caps={width, height, framerate}, buffer={pts}"`. Overrides
|
||||
`ignored-event-fields` for specified event types.
|
||||
* `ignored-event-types`: Default: `{ }`. List of event type names to not record
|
||||
* `logged-event-types`: Default: `NULL`. List of event type names to not record,
|
||||
if noone provided, all events are logged, except the ones defined in the
|
||||
`ignored-event-types`.
|
||||
* `expectations-dir`: Path to the directory where the expectations will be
|
||||
written if they don't exist, relative to the current working directory. By
|
||||
default the current working directory is used, but this setting is usually
|
||||
set automatically as part of the `%(validateflow)s` expansion to a correct
|
||||
path like `~/gst-validate/gst-integration-testsuites/flow-expectations/<test
|
||||
name>`.
|
||||
* `actual-results-dir`: Path to the directory where the events will be recorded.
|
||||
The expectation file will be compared to this. By default the current working
|
||||
directory is used, but this setting is usually set automatically as part of
|
||||
the `%(validateflow)s` expansion to the test log directory, i.e.
|
||||
`~/gst-validate/logs/validate/launch_pipeline/<test name>`.
|
||||
* `generate-expectations`: Default: unset. When set to `true` the expectation
|
||||
file will be written and no testing will be done and if set to `false`, the
|
||||
expectation file will be required. If a validateflow config is used without
|
||||
specifying any other parametters, the validateflow plugin will consider that
|
||||
all validateflow overrides will use that value.
|
||||
|
||||
|
||||
## Scenario actions
|
||||
|
||||
Scenarios with validateflow work in the same way as other tests. Often
|
||||
validatetests will use appsrc in order to control the flow of data precisely,
|
||||
possibly interleaving events in between. The following is a list of useful
|
||||
actions.
|
||||
|
||||
* `appsrc-push`: Pushes a buffer from an appsrc element and waits for the chain
|
||||
operation to finish. A path to a file is provided, optionally with an offset
|
||||
and/or size.
|
||||
* `appsrc-eos`: Queues an EOS event from the appsrc. The action finishes
|
||||
immediately at this point.
|
||||
* `stop`: Tears down the pipeline and stops the test.
|
||||
* `checkpoint`: Records a "checkpoint" message in all validateflow overrides,
|
||||
with an optional explanation message. This is useful to check certain events
|
||||
or buffers are sent at a specific moment in the scenario, and can also help
|
||||
to the comprehension of the scenario.
|
||||
|
||||
More details on these actions can be queried from the command line, like this:
|
||||
|
||||
``` bash
|
||||
gst-validate-1.0 --inspect-action-type appsrc-push
|
||||
```
|
166
docs/gst-validate-launcher.md
Normal file
166
docs/gst-validate-launcher.md
Normal file
|
@ -0,0 +1,166 @@
|
|||
---
|
||||
short-description: Integration testsuite builder and launcher
|
||||
...
|
||||
|
||||
# gst-validate-launcher
|
||||
|
||||
`gst-validate-launcher` is an application to run unit or integration testsuites
|
||||
providing a set of options and features to help debugging them.
|
||||
|
||||
## Run the GStreamer unit tests
|
||||
|
||||
Running GStreamer unit tests inside `gst-build` is as simple as doing:
|
||||
|
||||
```
|
||||
gst-validate-launcher check.gst*
|
||||
```
|
||||
|
||||
If you only want to run GStreamer core tests:
|
||||
|
||||
```
|
||||
gst-validate-launcher check.gstreamer*
|
||||
```
|
||||
|
||||
Or to run unit tests from gst-plugins-base
|
||||
|
||||
```
|
||||
gst-validate-launcher check.gst-plugins-base
|
||||
```
|
||||
|
||||
You can also run them inside valgrind with the `-vg` option or inside gdb with
|
||||
`--gdb` for example.
|
||||
|
||||
## Run the GstValidate default testsuite
|
||||
|
||||
GstValidate comes with a default testsuite to be executed on a default
|
||||
set of media samples. Those media samples are stored with `git-lfs` so
|
||||
you will need it to be able to launch the default testsuite.
|
||||
|
||||
We recommend using `gst-build` to setup everything needed to run the testsuite
|
||||
and you can simply do:
|
||||
|
||||
gst-validate-launcher validate
|
||||
|
||||
This will only launch the GstValidate tests and not other applications
|
||||
that might be supported (currently `ges-launch` is also supported and
|
||||
has its own default testsuite).
|
||||
|
||||
## Example of a testsuite implementation
|
||||
|
||||
To implement a testsuite, you will have to write some simple python code
|
||||
that defines the tests to be launched by `gst-validate-launcher`.
|
||||
|
||||
In this example, we will assume that you want to write a whole new
|
||||
testsuite based on your own media samples and [scenarios](GstValidateScenario). The
|
||||
set of media files and the testsuite implementation file will be
|
||||
structured as follow:
|
||||
|
||||
testsuite_folder/
|
||||
|-> testsuite.py
|
||||
|-> sample_files/
|
||||
|-> file.mp4
|
||||
|-> file1.mkv
|
||||
|-> file2.ogv
|
||||
|-> scenarios
|
||||
|-> scenario.scenario
|
||||
|-> scenario1.scenario
|
||||
|
||||
You should generate the `.media_info` files. To generate them for local
|
||||
files, you can use:
|
||||
|
||||
gst-validate-launcher --medias-paths /path/to/sample_files/ --generate-media-info
|
||||
|
||||
For remote streams, you should use
|
||||
`gst-validate-media-check-1.0`. For an http stream you can
|
||||
for example do:
|
||||
|
||||
gst-validate-media-check-GST_API_VERSION http://someonlinestream.com/thestream \
|
||||
--output-file /path/to/testsuite_folder/sample_files/thestream.stream_info
|
||||
|
||||
|
||||
The `gst-validate-launcher` will use the generated `.media_info` and
|
||||
`.stream_info` files to validate the tests as those contain the
|
||||
necessary information.
|
||||
|
||||
Then you will need to write the `testsuite.py` file. You can for example
|
||||
implement the following testsuite:
|
||||
|
||||
``` python
|
||||
"""
|
||||
The GstValidate custom testsuite
|
||||
"""
|
||||
|
||||
import os
|
||||
from launcher.baseclasses import MediaFormatCombination
|
||||
from launcher.apps.gstvalidate import *
|
||||
TEST_MANAGER = "validate"
|
||||
|
||||
KNOWN_ISSUES = {}
|
||||
|
||||
def setup_tests(test_manager, options):
|
||||
print("Setting up the custom testsuite")
|
||||
assets_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".", "samples_files"))
|
||||
options.add_paths(assets_dir)
|
||||
|
||||
# This step will register default data for the test manager:
|
||||
# - scenarios such as `play_15s`, `reverse_playback` etc.
|
||||
# - encoding formats such as "mp4,h264,mp3" etc.
|
||||
# - blacklist such as dash.media_check.*
|
||||
# - test generators:
|
||||
# - GstValidatePlaybinTestsGenerator
|
||||
# - GstValidateMediaCheckTestsGenerator
|
||||
# - GstValidateTranscodingTestsGenerator
|
||||
# This 'defaults' can be found in 'gst-devtools/validate/launcher/apps/gstvalidate.py#register_defaults'
|
||||
# test_manager.register_defaults()
|
||||
|
||||
# Add scenarios
|
||||
scenarios = []
|
||||
scenarios.append("play_5s")
|
||||
scenarios.append("seek_backward")
|
||||
test_manager.set_scenarios(scenarios)
|
||||
|
||||
# Add encoding formats used by the transcoding generator
|
||||
test_manager.add_encoding_formats([
|
||||
MediaFormatCombination("mp4", "mp3", "h264"),])
|
||||
|
||||
# Add generators
|
||||
# GstValidatePlaybinTestsGenerator needs at least one media file
|
||||
test_manager.add_generators([GstValidateMediaCheckTestsGenerator(test_manager)])
|
||||
# GstValidatePlaybinTestsGenerator needs at least one scenario
|
||||
test_manager.add_generators([GstValidatePlaybinTestsGenerator(test_manager)])
|
||||
# GstValidateTranscodingTestsGenerator needs at least one MediaFormatCombination
|
||||
test_manager.add_generators([GstValidateTranscodingTestsGenerator(test_manager)])
|
||||
|
||||
# list of combo to blacklist tests. Here it blacklists all tests with playback.seek_backward
|
||||
test_manager.set_default_blacklist([
|
||||
("custom_testsuite.file.playback.seek_backward.*",
|
||||
"Not supported by this testsuite."),])
|
||||
|
||||
# you can even pass known issues to bypass an existing error in your custom testsuite
|
||||
test_manager.add_expected_issues(KNOWN_ISSUES)
|
||||
return True
|
||||
```
|
||||
|
||||
Once this is done, you've got a testsuite that will:
|
||||
|
||||
- Run playbin pipelines on `file.mp4`, `file1.mkv` and `file2.ogv`>
|
||||
executing `play_5s` and `seek_backward` scenarios
|
||||
|
||||
- Transcode `file.mp4,` `file1.mkv` and `file2.ogv` to h264 and
|
||||
mp3 in a MP4 container
|
||||
|
||||
The only thing to do to run the testsuite is:
|
||||
|
||||
|
||||
gst-validate-launcher --testsuites-dir=/path/to/testsuite_folder/ testsuite
|
||||
|
||||
|
||||
# Invocation
|
||||
|
||||
You can find detailed information about the launcher by launching it:
|
||||
|
||||
gst-validate-launcher --help
|
||||
|
||||
You can list all the tests with:
|
||||
|
||||
gst-validate-launcher --testsuites-dir=/path/to/testsuite_folder/ testsuite -L
|
32
docs/gst-validate-media-check.md
Normal file
32
docs/gst-validate-media-check.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
short-description: Tool to test GStreamer media types discovery
|
||||
...
|
||||
|
||||
# gst-validate-media-check
|
||||
|
||||
`gst-validate-media-check` is command line tool checking that media
|
||||
files discovering works properly with `gst-discoverer` over multiple
|
||||
runs. It needs a reference text file containing valid information about
|
||||
a media file (which can be generated with the same tool) and then it
|
||||
will be able to check that the reference matches what will be reported
|
||||
by `gst-discoverer` in the following runs.
|
||||
|
||||
For example, given that we have a valid `reference.media_info` file, we
|
||||
can run:
|
||||
|
||||
gst-validate-media-check-GST_API_VERSION file:///./file.ogv --expected-results reference.media_info
|
||||
|
||||
It will then output any error encountered and return an exit code
|
||||
different from 0 if any error is found.
|
||||
|
||||
# Invocation
|
||||
|
||||
`gst-validate-media-check` takes an URI to analyze and some extra
|
||||
options to control the output.
|
||||
|
||||
## Options
|
||||
|
||||
* `-o`, `--output-file`: The output file to store the results.
|
||||
* `-f`, `--full`: Fully analize the file frame by frame.
|
||||
* `-e`, `--expected-results`: Path to file containing the expected results (or the last results
|
||||
found) for comparison with new results.
|
110
docs/gst-validate-scenarios.md
Normal file
110
docs/gst-validate-scenarios.md
Normal file
|
@ -0,0 +1,110 @@
|
|||
---
|
||||
title: Scenarios
|
||||
short-description: The GstValidate Scenario format
|
||||
...
|
||||
|
||||
# GstValidate Scenario File Format
|
||||
|
||||
To be able to define a list of actions to execute on a [`GstPipeline`],
|
||||
a dedicated file format is used. The name of the scenario is the name of
|
||||
the file without its `.scenario` extension. The scenario file format is
|
||||
based on the [`GstStructure`] serialized format which is a basic, type
|
||||
aware, key value format. It takes the type of the action in the first
|
||||
comma separated field, and then some key value pairs in the form
|
||||
`parameter=value` separated by commas. The values type will be guessed
|
||||
if not casted as in `parameter=(string)value`. You can force the type
|
||||
guessing system to actually know what type you want by giving it the
|
||||
right hints. For example to make sure the value is a double, you should
|
||||
add a decimal (ie. `1` will be considered as a `int`, but `1.0` will be
|
||||
considered as a `double` and `"1.0"` will be considered as a `string`).
|
||||
|
||||
For example to represent a seek action, you should add the following
|
||||
line in the `.scenario` file.
|
||||
|
||||
seek, playback-time=10.0, start=0.0, flags=accurate+flush
|
||||
|
||||
The files to be used as scenario should have a `.scenario` extension and
|
||||
should be placed either in
|
||||
`$USER_DATA_DIR/gstreamer-1.0/validate/scenarios` ,
|
||||
`$GST_DATADIR/gstreamer-1.0/validate/scenarios` or in a path defined in
|
||||
the \$GST\_VALIDATE\_SCENARIOS\_PATH environment variable.
|
||||
|
||||
Each line in the `.scenario` file represent an action (you can also use
|
||||
`\ ` at the end of a line write a single action on multiple lines).
|
||||
Usually you should start you scenario with a `meta` structure
|
||||
in order for the user to have more information about the
|
||||
scenario. It can contain a `summary` field which is a string explaining
|
||||
what the scenario does and then several info fields about the scenario.
|
||||
You can find more info about it running:
|
||||
|
||||
gst-validate-1.0 --inspect-action-type action_type_name
|
||||
|
||||
So a basic scenario file that will seek three times and stop would look
|
||||
like:
|
||||
|
||||
```
|
||||
meta, summary="Seeks at 1.0 to 2.0 then at \
|
||||
3.0 to 0.0 and then seeks at \
|
||||
1.0 to 2.0 for 1.0 second (between 2.0 and 3.0).", \
|
||||
seek=true, duration=5.0, min-media-duration=4.0
|
||||
seek, playback-time=1.0, rate=1.0, start=2.0, flags=accurate+flush
|
||||
seek, playback-time=3.0, rate=1.0, start=0.0, flags=accurate+flush
|
||||
seek, playback-time=1.0, rate=1.0, start=2.0, stop=3.0, flags=accurate+flush
|
||||
```
|
||||
|
||||
Many action types have been implemented to help users define their own
|
||||
scenarios. For example there are:
|
||||
|
||||
- `seek`: Seeks into the stream.
|
||||
- `play`: Set the pipeline state to `GST_STATE_PLAYING`.
|
||||
- `pause`: Set the pipeline state to `GST_STATE_PAUSED`.
|
||||
- `stop`: Stop the execution of the pipeline.
|
||||
|
||||
> **NOTE**: This action actually posts a [`GST_MESSAGE_REQUEST_STATE`]
|
||||
> message requesting [`GST_STATE_NULL`] on the bus and the application
|
||||
> should quit.
|
||||
|
||||
To get all the details about the registered action types, you can list
|
||||
them all with:
|
||||
|
||||
```
|
||||
gst-validate-1.0 --inspect-action-type
|
||||
```
|
||||
|
||||
and to include transcoding specific action types:
|
||||
|
||||
```
|
||||
gst-validate-transcoding-1.0 --inspect-action-type
|
||||
```
|
||||
|
||||
Many scenarios are distributed with `gst-validate`, you can list them
|
||||
all using:
|
||||
|
||||
```
|
||||
gst-validate-1.0 --list-scenarios
|
||||
```
|
||||
|
||||
You can find more information about the scenario implementation and
|
||||
action types in the [`GstValidateScenario` section].
|
||||
|
||||
[`GstPipeline`]: GstPipeline
|
||||
[`GstStructure`]: GstStructure
|
||||
[`GST_MESSAGE_REQUEST_STATE`]: GST_MESSAGE_REQUEST_STATE
|
||||
[`GST_STATE_NULL`]: GST_STATE_NULL
|
||||
[`GstValidateScenario` section]: GstValidateScenario
|
||||
|
||||
## Default variables
|
||||
|
||||
Any action can use the default variables:
|
||||
|
||||
- `$(position)`: The current position in the pipeline as reported by
|
||||
[gst_element_query_position()](gst_element_query_position)
|
||||
- `$(duration)`: The current duration of the pipeline as reported by
|
||||
[gst_element_query_duration()](gst_element_query_duration)
|
||||
- `$(TMPDIR)`: The default temporary directory as returned by `g_get_tmp_dir`.
|
||||
- `$(SCENARIO_PATH)`: The path of the running scenario.
|
||||
- `$(SCENARIO_DIR)`: The directory the running scenario is in.
|
||||
- `$(SCENARIO_NAME)`: The name the running scenario
|
||||
|
||||
|
||||
It is also possible to set variables in scenario with the `set-vars` action.
|
118
docs/gst-validate-test-file.md
Normal file
118
docs/gst-validate-test-file.md
Normal file
|
@ -0,0 +1,118 @@
|
|||
---
|
||||
title: Test file
|
||||
short-description: GstValidate test file
|
||||
...
|
||||
|
||||
# GstValidate Test file
|
||||
|
||||
A `.validatetest` file describes a fully contained validate test case. It
|
||||
includes the arguments of the tool supposed to be used to run the test as well
|
||||
as possibly a [configuration](gst-validate-config.md) and a set of action to
|
||||
describe the validate [scenario](gst-validate-scenarios.md).
|
||||
|
||||
# The file format
|
||||
|
||||
A validate test file requires a `meta` structure which contains the same
|
||||
information as the [scenario](gst-validate-scenarios.md) `meta` with some
|
||||
additional fields described below. The `meta` structure should be either the
|
||||
first or the one following the `set-globals` structure. The `set-globals`
|
||||
structures allows you to set global variables for the rest of the
|
||||
`.validatetest` file and is a free form variables setter. For example you can
|
||||
do:
|
||||
|
||||
``` yaml
|
||||
set-globals, media_dir=$(test_dir)/../../media
|
||||
```
|
||||
|
||||
## Tool arguments
|
||||
|
||||
In the case of [`gst-validate`](gst-validate.md) it **has to** contain an
|
||||
`args` field with `gst-validate` argv arguments like:
|
||||
|
||||
``` yaml
|
||||
# This is the default tool so it is not mandatory for the `gst-validate` tool
|
||||
tool = "gst-validate-$(gst_api_version)",
|
||||
args = {
|
||||
# pipeline description
|
||||
videotestrc num-buffers=2 ! $(videosink),
|
||||
# Random extra argument
|
||||
--set-media-info $(test-dir)/some.media_info
|
||||
}
|
||||
```
|
||||
|
||||
## configs
|
||||
|
||||
The `configs` field is an array of structures containing the same content as
|
||||
usual [configs](gst-validate-config.md) files.
|
||||
|
||||
For example:
|
||||
|
||||
``` yaml
|
||||
configs = {
|
||||
# Set videotestsrc0 pattern value to `blue`
|
||||
"core, action=set-property, target-element-name=videotestsrc0, property-name=pattern, property-value=blue",
|
||||
"$(validateflow), pad=sink1:sink, caps-properties={ width, height };",
|
||||
}
|
||||
```
|
||||
|
||||
Note: Since this is GstStructure synthax, we need to have the structures in the
|
||||
array as strings/within quotes.
|
||||
|
||||
## expected-issues
|
||||
|
||||
The `expected-issues` field is an array of `expected-issue` structures containing
|
||||
information about issues to expect (which can be known bugs or not).
|
||||
|
||||
Use `gst-validate-1.0 --print-issue-types` to print information about all issue types.
|
||||
|
||||
For example:
|
||||
|
||||
``` yaml
|
||||
expected-issues = {
|
||||
"expected-issue, issue-id=scenario::not-ended",
|
||||
}
|
||||
```
|
||||
|
||||
Note: Since this is GstStructure synthax, we need to have the structures in the
|
||||
array as strings/within quotes.
|
||||
|
||||
### Fields:
|
||||
|
||||
* `issue-id`: (string): Issue ID - Mandatory if `summary` is not provided.
|
||||
* `summary`: (string): Summary - Mandatory if `issue-id` is not provided.
|
||||
* `details`: Regex string to match the issue details `detected-on`: (string):
|
||||
The name of the element the issue happened on `level`: (string):
|
||||
Issue level
|
||||
* `sometimes`: (boolean): Default: `false` - Wheteher the issue happens only
|
||||
sometimes if `false` and the issue doesn't happen, an error will
|
||||
be issued.
|
||||
* `issue-url`: (string): The url of the issue in the bug tracker if the issue is
|
||||
a bug.
|
||||
|
||||
### Variables
|
||||
|
||||
The same way
|
||||
|
||||
Validate testfile will define some variables to make those files relocable:
|
||||
|
||||
* `$(test_dir)`: The directory where the `.validatetest` file is in.
|
||||
|
||||
* `$(test_name)`: The name of the test file (without extension).
|
||||
|
||||
* `$(test_name_dir)`: The name of the test directory (test_name with folder
|
||||
separator instead of `.`).
|
||||
|
||||
* `$(validateflow)`: The validateflow structure name with the default/right
|
||||
values for the `expectations-dir` and `actual-results-dir`
|
||||
fields. See [validateflow](gst-validate-flow.md) for more
|
||||
information.
|
||||
|
||||
* `$(videosink)`: The GStreamer videosink to use if the test can work with
|
||||
different sinks for the video. It allows the tool to use
|
||||
fakesinks when the user doesn't want to have visual feedback
|
||||
for example.
|
||||
|
||||
* `$(audiosink)`: The GStreamer audiosink to use if the test can work with
|
||||
different sinks for the audio. It allows the tool to use
|
||||
fakesinks when the user doesn't want to have audio feedback
|
||||
for example.
|
126
docs/gst-validate-transcoding.md
Normal file
126
docs/gst-validate-transcoding.md
Normal file
|
@ -0,0 +1,126 @@
|
|||
---
|
||||
short-description: Tool to test GStreamer components
|
||||
...
|
||||
|
||||
# gst-validate-transcoding
|
||||
|
||||
`gst-validate-transcoding` is tool to create media files transcoding
|
||||
pipelines running inside the GstValidate monitoring infrastructure.
|
||||
|
||||
You can for example transcode any media file to Vorbis audio + VP8 video
|
||||
in a WebM container by doing:
|
||||
|
||||
gst-validate-transcoding-GST_API_VERSION file:///./file.ogg file:///.../transcoded.webm -o 'video/webm:video/x-vp8:audio/x-vorbis'
|
||||
|
||||
`gst-validate-transcoding` will list every issue encountered during the
|
||||
execution of the transcoding operation in a human readable report like
|
||||
the one below:
|
||||
|
||||
issue : buffer is out of the segment range Detected on theoradec0.srcpad
|
||||
at 0:00:00.096556426 Details : buffer is out of segment and shouldn't be
|
||||
pushed. Timestamp: 0:00:25.000 - duration: 0:00:00.040 Range:
|
||||
0:00:00.000 - 0:00:04.520 Description : buffer being pushed is out of
|
||||
the current segment's start-stop range. Meaning it is going to be
|
||||
discarded downstream without any use
|
||||
|
||||
The return code of the process will be 18 in case a `CRITICAL` issue has
|
||||
been found.
|
||||
|
||||
## The encoding profile serialization format
|
||||
|
||||
This is the serialization format of a [GstEncodingProfile](GstEncodingProfile).
|
||||
|
||||
Internally the transcoding application uses [GstEncodeBin](encodebin).
|
||||
`gst-validate-transcoding-GST_API_VERSION` uses its own serialization
|
||||
format to describe the [`GstEncodeBin.profile`](encodebin:profile) property of the
|
||||
encodebin.
|
||||
|
||||
The simplest serialized profile looks like:
|
||||
|
||||
muxer_source_caps:videoencoder_source_caps:audioencoder_source_caps
|
||||
|
||||
For example to encode a stream into a WebM container, with an OGG audio
|
||||
stream and a VP8 video stream, the serialized [GstEncodingProfile](GstEncodingProfile)
|
||||
will look like:
|
||||
|
||||
video/webm:video/x-vp8:audio/x-vorbis
|
||||
|
||||
You can also set the preset name of the encoding profile using the
|
||||
caps+preset\_name syntax as in:
|
||||
|
||||
video/webm:video/x-vp8+youtube-preset:audio/x-vorbis
|
||||
|
||||
Moreover, you can set the [presence](gst_encoding_profile_set_presence) property
|
||||
of an encoding profile using the `|presence` syntax as in:
|
||||
|
||||
video/webm:video/x-vp8|1:audio/x-vorbis
|
||||
|
||||
This field allows you to specify how many times maximum a
|
||||
[GstEncodingProfile](GstEncodingProfile) can be used inside an encodebin.
|
||||
|
||||
You can also use the `restriction_caps->encoded_format_caps` syntax to
|
||||
specify the [restriction caps](GstEncodingProfile:restriction-caps)
|
||||
to be set on a [GstEncodingProfile](GstEncodingProfile). It
|
||||
corresponds to the restriction [GstCaps](GstCaps) to apply before the encoder
|
||||
that will be used in the profile. The fields present in restriction caps
|
||||
are properties of the raw stream (that is, before encoding), such as
|
||||
height and width for video and depth and sampling rate for audio. This
|
||||
property does not make sense for muxers.
|
||||
|
||||
To force a video stream to be encoded with a Full HD resolution (using
|
||||
WebM as the container format, VP8 as the video codec and Vorbis as the
|
||||
audio codec), you should use:
|
||||
|
||||
video/webm:video/x-raw,width=1920,height=1080->video/x-vp8:audio/x-vorbis
|
||||
|
||||
### Some serialized encoding formats examples:
|
||||
|
||||
MP3 audio and H264 in MP4:
|
||||
|
||||
<div class="informalexample">
|
||||
|
||||
video/quicktime,variant=iso:video/x-h264:audio/mpeg,mpegversion=1,layer=3
|
||||
|
||||
</div>
|
||||
|
||||
Vorbis and theora in OGG:
|
||||
|
||||
<div class="informalexample">
|
||||
|
||||
application/ogg:video/x-theora:audio/x-vorbis
|
||||
|
||||
</div>
|
||||
|
||||
AC3 and H264 in MPEG-TS:
|
||||
|
||||
<div class="informalexample">
|
||||
|
||||
video/mpegts:video/x-h264:audio/x-ac3
|
||||
|
||||
</div>
|
||||
|
||||
# Invocation
|
||||
|
||||
`gst-validate-transcoding` takes and input URI and an output URI, plus a
|
||||
few options to control how transcoding should be tested.
|
||||
|
||||
## Options
|
||||
|
||||
* `--set-scenario`: Let you set a scenario, it can be a full path to a scenario file or
|
||||
the name of the scenario (name of the file without the `.scenario`
|
||||
extension).
|
||||
* `-l`, `--list-scenarios`: List the avalaible scenarios that can be run.
|
||||
* `--scenarios-defs-output-file`: The output file to store scenarios details. Implies
|
||||
`--list-scenario`.
|
||||
* `-t`, `--inspect-action-type`: Inspect the avalaible action types with which to write scenarios if
|
||||
no parameter passed, it will list all avalaible action types
|
||||
otherwize will print the full description of the wanted types.
|
||||
* `--set-configs`: Let you set a config scenario. The scenario needs to be set as
|
||||
`config`. You can specify a list of scenarios separated by `:`. It
|
||||
will override the GST\_VALIDATE\_SCENARIO environment variable.
|
||||
* `-e`, `--eos-on-shutdown`: If an EOS event should be sent to the pipeline if an interrupt is
|
||||
received, instead of forcing the pipeline to stop. Sending an EOS
|
||||
will allow the transcoding to finish the files properly before
|
||||
exiting.
|
||||
* `-r`, `--force-reencoding`: Whether to try to force reencoding, meaning trying to only remux if
|
||||
possible, defaults to `TRUE`.
|
58
docs/gst-validate.md
Normal file
58
docs/gst-validate.md
Normal file
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
short-description: Tool to test GStreamer components
|
||||
...
|
||||
|
||||
# gst-validate
|
||||
|
||||
`gst-validate` is the simplest `gst-launch`-like pipeline launcher
|
||||
running inside GstValidate monitoring infrastructure. Monitors are added
|
||||
to it to identify issues in the used elements. At the end it will print
|
||||
a report with some information about all the issues encountered during
|
||||
its run. To view issues as they are detected, set the environment
|
||||
variable `GST_DEBUG=validate:2`{.shell} and they will get printed in the
|
||||
GStreamer debug log. You can basically run any [GstPipeline](GstPipeline) pipeline
|
||||
using this tool. If you are not familiar with `gst-launch` syntax,
|
||||
please refer to `gst-launch`'s documentation.
|
||||
|
||||
Simple playback pipeline:
|
||||
|
||||
gst-validate-1.0 playbin uri=file:///path/to/some/media/file
|
||||
|
||||
Transcoding pipeline:
|
||||
|
||||
gst-validate-1.0 filesrc location=/media/file/location ! qtdemux name=d ! queue \
|
||||
! x264enc ! h264parse ! mpegtsmux name=m ! progressreport \
|
||||
! filesink location=/root/test.ts d. ! queue ! faac ! m.
|
||||
|
||||
It will list each issue that has been encountered during the execution
|
||||
of the specified pipeline in a human readable report like:
|
||||
|
||||
issue : buffer is out of the segment range Detected on theoradec0.srcpad at 0:00:00.096556426
|
||||
|
||||
Details : buffer is out of segment and shouldn't be pushed. Timestamp: 0:00:25.000 - duration: 0:00:00.040 Range: 0:00:00.000 - 0:00:04.520
|
||||
Description : buffer being pushed is out of the current segment's start-stop range. Meaning it is going to be discarded downstream without any use
|
||||
|
||||
The return code of the process will be 18 in case a `CRITICAL` issue has
|
||||
been found.
|
||||
|
||||
# Invocation
|
||||
|
||||
`gst-validate` takes a mandatory description of the pipeline to launch,
|
||||
similar to `gst-launch`, and some extra options.
|
||||
|
||||
## Options
|
||||
|
||||
* `--set-scenario`: Let you set a scenario, it can be a full path to a scenario file or
|
||||
the name of the scenario (name of the file without the `.scenario`
|
||||
extension).
|
||||
* `-l`, `--list-scenarios`: List the avalaible scenarios that can be run.
|
||||
* `--scenarios-defs-output-file`: The output file to store scenarios details. Implies
|
||||
`--list-scenario`.
|
||||
* `-t`, `--inspect-action-type`: Inspect the avalaible action types with which to write scenarios if
|
||||
no parameter passed, it will list all avalaible action types
|
||||
otherwize will print the full description of the wanted types.
|
||||
* `--set-media-info`: Set a media\_info XML file descriptor to share information about the
|
||||
media file that will be reproduced.
|
||||
* `--set-configs`: Let you set a config scenario. The scenario needs to be set as
|
||||
`config`. You can specify a list of scenarios separated by "`:`". It
|
||||
will override the GST\_VALIDATE\_SCENARIO environment variable.
|
26
docs/index.md
Normal file
26
docs/index.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# GStreamer Validate
|
||||
|
||||
GstValidate is a tool that allows GStreamer developers to check that the
|
||||
GstElements they write behave the way they are supposed to. It was first
|
||||
started to provide plug-ins developers with a tool to check that they
|
||||
use the framework the proper way.
|
||||
|
||||
GstValidate implements a monitoring logic that allows the system to
|
||||
check that the elements of a GstPipeline respect some rules GStreamer
|
||||
components have to follow to make them properly interact together. For
|
||||
example, a GstValidatePadMonitor will make sure that if we receive a
|
||||
GstSegment from upstream, an equivalent segment is sent downstream
|
||||
before any buffer gets out.
|
||||
|
||||
Then GstValidate implements a reporting system that allows users to get
|
||||
detailed informations about what was not properly handled by the
|
||||
elements. The generated reports are ordered by level of importance from
|
||||
"issue" to "critical".
|
||||
|
||||
Some tools have been implemented to help developers validate and test
|
||||
their GstElement, see [gst-validate](gst-validate.md) for example.
|
||||
|
||||
On top of that, the notion of a [validation scenario](gst-validate-scenarios.md)
|
||||
has been implemented so that developers can easily execute a set of actions on
|
||||
pipelines to test real world interactive cases and reproduce existing
|
||||
issues in a convenient way.
|
71
docs/meson.build
Normal file
71
docs/meson.build
Normal file
|
@ -0,0 +1,71 @@
|
|||
build_hotdoc = false
|
||||
|
||||
if meson.is_cross_build()
|
||||
if get_option('doc').enabled()
|
||||
error('Documentation enabled but building the doc while cross building is not supported yet.')
|
||||
endif
|
||||
|
||||
message('Documentation not built as building it while cross building is not supported yet.')
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
hotdoc_p = find_program('hotdoc', required: get_option('doc'))
|
||||
if not hotdoc_p.found()
|
||||
message('Hotdoc not found, not building the documentation')
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
required_hotdoc_extensions = ['gi-extension']
|
||||
if not build_gir
|
||||
if get_option('doc').enabled()
|
||||
error('Documentation enabled but introspection not built.')
|
||||
endif
|
||||
|
||||
message('Introspection not built, can\'t build the documentation')
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
hotdoc = import('hotdoc')
|
||||
foreach extension: required_hotdoc_extensions
|
||||
if not hotdoc.has_extensions(extension)
|
||||
if get_option('doc').enabled()
|
||||
error('Documentation enabled but @0@ missing'.format(extension))
|
||||
endif
|
||||
|
||||
message('@0@ extension not found, not building documentation'.format(extension))
|
||||
subdir_done()
|
||||
endif
|
||||
endforeach
|
||||
|
||||
excludes = ['gettext.h',
|
||||
'gst-validate-internal.h',
|
||||
'gst-validate-i18n-lib.c'
|
||||
]
|
||||
|
||||
build_hotdoc = true
|
||||
validate_excludes = []
|
||||
foreach f: excludes
|
||||
validate_excludes += [join_paths(meson.current_source_dir(), '..',
|
||||
'validate', 'gst', 'validate', f)]
|
||||
endforeach
|
||||
|
||||
validate_sources = []
|
||||
foreach f: gstvalidate_headers + gstvalidate_sources
|
||||
validate_sources += [join_paths(meson.current_source_dir(), '..',
|
||||
'validate', 'gst', 'validate', f)]
|
||||
endforeach
|
||||
|
||||
hotdoc = import('hotdoc')
|
||||
plugins_doc = []
|
||||
libs_doc = [hotdoc.generate_doc('gst-devtools',
|
||||
project_version: apiversion,
|
||||
sitemap: 'sitemap.txt',
|
||||
index: 'index.md',
|
||||
gi_c_sources: validate_sources,
|
||||
gi_c_source_filters: validate_excludes,
|
||||
gi_index: 'gi-index.md',
|
||||
gi_smart_index: true,
|
||||
gi_sources: [validate_gir[0].full_path()],
|
||||
disable_incremental_build: true,
|
||||
dependencies : [validate_dep],
|
||||
)]
|
3
docs/plugins/index.md
Normal file
3
docs/plugins/index.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# GstValidate plugins
|
||||
|
||||
GstValidate offers a plugin system to extend the checks and add new functionnality
|
87
docs/plugins/ssim.md
Normal file
87
docs/plugins/ssim.md
Normal file
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
title: SSIM plugin
|
||||
short_description: GstValidate plugin to detect frame corruptions
|
||||
...
|
||||
|
||||
# SSIM plugin
|
||||
|
||||
GstValidate plugin to run the ssim algorithm on the buffers flowing in the
|
||||
pipeline to find regressions and detect frame corruptions.
|
||||
It allows you to generate image files from the buffers flowing in the pipeline
|
||||
(either as raw in the many formats supported by GStreamer or as png) and then
|
||||
check them against pre generated, reference images.
|
||||
|
||||
The ssim algorithm will set a value of 1.0 when images are perfectly identical,
|
||||
and -1.0 if they have nothing in common. By default we consider images as similar
|
||||
if they have at least a ssim value of 0.95 but you can override it defining the value
|
||||
under which the test will be considered as failed.
|
||||
|
||||
Errors are reported on the GstValidate reporting system. You can also ask
|
||||
the plugin to generate grey scale output images. Those will be named in a way
|
||||
that should lets you precisely see where and how the test failed.
|
||||
|
||||
# Configuration
|
||||
|
||||
The configuration of the plugin is done through a validate configuration file,
|
||||
specified with the %GST_VALIDATE_CONFIG environment variable. Each line starting
|
||||
with 'ssim,' will configure the ssim plugin. In practice each configuration statement
|
||||
will lead to the creation of a #GstValidateOverride object which will then dump
|
||||
image files and if wanted compare those with a set of reference images.
|
||||
|
||||
The following parameters can be passed in the configuration file:
|
||||
- element-classification: The target element classification as define in
|
||||
gst_element_class_set_metadata
|
||||
- output-dir: The directory in which the image files will be saved
|
||||
- min-avg-priority: (default 0.95): The minimum average similarity
|
||||
under which we consider the test as failing
|
||||
- min-lowest-priority: (default 1): The minimum 'lowest' similarity
|
||||
under which we consider the test as failing
|
||||
- reference-images-dir: Define the directory in which the files to be
|
||||
compared can be found
|
||||
- result-output-dir: The folder in which to store resulting grey scale
|
||||
images when the test failed. In that folder you will find images
|
||||
with the structural difference between the expected result and the actual
|
||||
result.
|
||||
- output-video-format: The format in which you want the images to be saved
|
||||
- reference-video-format: The format in which the reference images are stored
|
||||
- check-recurrence: The recurrence in seconds (as float) the frames should
|
||||
be dumped and checked.By default it is GST_CLOCK_TIME_NONE, meaning each
|
||||
and every frame is checked. Not that in any case, after a discontinuity
|
||||
in the stream (after a seek or a change in the video format for example)
|
||||
a check is done. And if recurrence == 0, images will be checked only after
|
||||
such discontinuity
|
||||
- is-config: Property letting the plugin know that the config line is exclusively
|
||||
used to configure the following configuration expressions. In practice this
|
||||
means that it will change the default values for the other configuration
|
||||
expressions.
|
||||
- framerate: (GstFraction): The framerate to use to compute frame number from
|
||||
timestamp, allowing to compare frames by 'frame number' instead of trying to
|
||||
match timestamp between reference images and output images.
|
||||
|
||||
# Example #
|
||||
|
||||
Let's take a special configuration where we want to compare frames that are
|
||||
outputted by a video decoder with the ones after a agingtv element we would
|
||||
call my_agingtv. We force to check one frame every 5.0 seconds only (with
|
||||
check-recurrence=5.0) so the test is fast.
|
||||
|
||||
The configuration file:
|
||||
|
||||
``` shell
|
||||
core, action=set-property, target-element-klass=Sink, property-name=sync, property-value=false
|
||||
|
||||
validatessim, is-config=true, output-video-format="I420", reference-video-format="I420"
|
||||
validatessim, element-classification="Video/Decoder", output-dir=/tmp/test/before-agingtv/
|
||||
validatessim, element-name=my_agingtv, output-dir=/tmp/test/after-agingtv/, \
|
||||
reference-images-dir=/tmp/test/before-agingtv/, \
|
||||
result-output-dir=/tmp/test/failures, check-recurrence=5.0
|
||||
```
|
||||
|
||||
Save that content in a file called check_agingtv_ssim.config
|
||||
|
||||
|
||||
## Launch the pipeline
|
||||
|
||||
``` shell
|
||||
GST_VALIDATE_CONFIG=check_agingtv_ssim.config gst-validate-1.0-debug uridecodebin uri=file://a/file ! videoconvert ! agingtv name=my_agingtv ! videoconvert ! autovideosink
|
||||
```
|
3
docs/plugins/validateflow.md
Normal file
3
docs/plugins/validateflow.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
redirect: gst-validate-flow.md
|
||||
...
|
16
docs/sitemap.txt
Normal file
16
docs/sitemap.txt
Normal file
|
@ -0,0 +1,16 @@
|
|||
index.md
|
||||
gst-validate.md
|
||||
gst-validate-transcoding.md
|
||||
gst-validate-media-check.md
|
||||
gst-validate-launcher.md
|
||||
gst-validate-scenarios.md
|
||||
gst-validate-test-file.md
|
||||
gst-validate-config.md
|
||||
gst-validate-environment-variables.md
|
||||
gst-validate-action-types.md
|
||||
ges-validate-action-types.md
|
||||
gst-validate-flow.md
|
||||
gi-index
|
||||
plugins/index.md
|
||||
plugins/ssim.md
|
||||
plugins/validateflow.md
|
384
gst-devtools.doap
Normal file
384
gst-devtools.doap
Normal file
|
@ -0,0 +1,384 @@
|
|||
<Project
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
|
||||
xmlns="http://usefulinc.com/ns/doap#"
|
||||
xmlns:foaf="http://xmlns.com/foaf/0.1/"
|
||||
xmlns:admin="http://webns.net/mvcb/">
|
||||
|
||||
<name>GStreamer development and validation tools</name>
|
||||
<shortname>gstreamer</shortname>
|
||||
<homepage rdf:resource="https://gstreamer.freedesktop.org/modules/gstreamer.html" />
|
||||
<created>1999-10-31</created>
|
||||
<shortdesc xml:lang="en">
|
||||
GStreamer development and validation tools including GstValidate, a testing
|
||||
framework aiming at providing GStreamer developers tools that check the
|
||||
GstElements they write behave the way they are supposed to.
|
||||
</shortdesc>
|
||||
<description xml:lang="en">
|
||||
GstValidate is a tool that allows GStreamer developers to check that the
|
||||
GstElements they write behave the way they are supposed to. It was first
|
||||
started to provide plug-ins developers with a tool to check that they use the
|
||||
framework the proper way.
|
||||
|
||||
GstValidate implements a monitoring logic that allows the system to check that
|
||||
the elements of a GstPipeline respect some rules GStreamer components have to
|
||||
follow so that elements can properly interact together. For example, a
|
||||
GstValidatePadMonitor will make sure that if we receive a GstSegment from
|
||||
upstream, an equivalent segment is sent downstream before any buffer gets out.
|
||||
|
||||
Then GstValidate implements a reporting system that allows users to get
|
||||
detailed informations about what was not properly handle in elements. The
|
||||
reports are order by level of importance from "issue" to "critical".
|
||||
|
||||
Some tools have been implemented to help the developer validate and test their
|
||||
GstElement, you can have a look at the command line tools section to find more
|
||||
information
|
||||
|
||||
On top of those tools, the notion of scenario has been implemented so that
|
||||
developers can easily execute a set of actions on pipelines and thus test real
|
||||
world interactive cases and reproduce existing issues in a convenient way.
|
||||
</description>
|
||||
<category></category>
|
||||
<bug-database rdf:resource="https://gitlab.freedesktop.org/gstreamer/gst-devtools/issues/" />
|
||||
<screenshots></screenshots>
|
||||
<mailing-list rdf:resource="https://lists.freedesktop.org/mailman/listinfo/gstreamer-devel" />
|
||||
<programming-language>C</programming-language>
|
||||
<license rdf:resource="http://usefulinc.com/doap/licenses/lgpl" />
|
||||
<download-page rdf:resource="https://gstreamer.freedesktop.org/download/" />
|
||||
|
||||
<repository>
|
||||
<GitRepository>
|
||||
<location rdf:resource="https://gitlab.freedesktop.org/gstreamer/gst-devtools"/>
|
||||
<browse rdf:resource="https://gitlab.freedesktop.org/gstreamer/gst-devtools/"/>
|
||||
</GitRepository>
|
||||
</repository>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.19.2</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2021-09-23</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-devtools/gst-devtools-1.19.2.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.19.1</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2021-06-01</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-devtools/gst-devtools-1.19.1.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.18.0</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2020-09-08</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-devtools/gst-devtools-1.18.0.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.17.90</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2020-08-20</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-devtools/gst-devtools-1.17.90.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.17.2</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2020-07-03</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-devtools/gst-devtools-1.17.2.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.17.1</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2020-06-19</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-devtools/gst-devtools-1.17.1.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.16.0</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2019-04-19</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.16.0.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.15.90</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2019-04-11</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.15.90.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.15.2</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2019-02-26</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.15.2.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.15.1</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2019-01-17</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.15.1.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.14.0</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2018-03-19</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.14.0.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.13.91</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2018-03-13</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.13.91.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.13.90</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2018-03-03</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.13.90.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.13.1</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2018-02-15</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.13.1.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.12.4</revision>
|
||||
<branch>1.12</branch>
|
||||
<name></name>
|
||||
<created>2017-12-07</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.12.4.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.12.3</revision>
|
||||
<branch>1.12</branch>
|
||||
<name></name>
|
||||
<created>2017-09-18</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.12.3.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.12.2</revision>
|
||||
<branch>1.12</branch>
|
||||
<name></name>
|
||||
<created>2017-07-14</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.12.2.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.12.1</revision>
|
||||
<branch>1.12</branch>
|
||||
<name></name>
|
||||
<created>2017-06-20</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.12.1.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.12.0</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2017-05-04</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.12.0.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.11.91</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2017-04-27</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.11.91.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.11.90</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2017-04-07</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.11.90.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.11.2</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2017-02-24</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.11.2.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.11.1</revision>
|
||||
<branch>master</branch>
|
||||
<name></name>
|
||||
<created>2017-01-12</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.11.1.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<Version>
|
||||
<revision>1.10.0</revision>
|
||||
<branch>master</branch>
|
||||
<created>2016-11-01</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.10.0.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<Version>
|
||||
<revision>1.9.90</revision>
|
||||
<branch>master</branch>
|
||||
<created>2016-09-30</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.9.90.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<Version>
|
||||
<revision>1.9.2</revision>
|
||||
<branch>master</branch>
|
||||
<created>2016-09-01</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.9.2.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<Version>
|
||||
<revision>1.9.1</revision>
|
||||
<branch>master</branch>
|
||||
<created>2016-06-06</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.9.1.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<Version>
|
||||
<revision>1.8.0</revision>
|
||||
<branch>master</branch>
|
||||
<created>2016-03-24</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.8.0.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<Version>
|
||||
<revision>1.7.91</revision>
|
||||
<branch>master</branch>
|
||||
<created>2016-03-15</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.7.91.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<Version>
|
||||
<revision>1.7.90</revision>
|
||||
<branch>master</branch>
|
||||
<created>2016-03-01</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.7.90.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<Version>
|
||||
<revision>1.6.0</revision>
|
||||
<branch>1.6</branch>
|
||||
<created>2015-09-25</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.6.0.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<Version>
|
||||
<revision>1.5.90</revision>
|
||||
<branch>1.5</branch>
|
||||
<created>2015-08-20</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.5.90.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.5.2</revision>
|
||||
<branch>1.5</branch>
|
||||
<name></name>
|
||||
<created>2014-09-29</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.5.2.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<release>
|
||||
<Version>
|
||||
<revision>1.4.0</revision>
|
||||
<branch>1.4</branch>
|
||||
<name></name>
|
||||
<created>2014-09-29</created>
|
||||
<file-release rdf:resource="https://gstreamer.freedesktop.org/src/gst-validate/gst-validate-1.4.0.tar.xz" />
|
||||
</Version>
|
||||
</release>
|
||||
|
||||
<maintainer>
|
||||
<foaf:Person>
|
||||
<foaf:name>Thibault Saunier</foaf:name>
|
||||
</foaf:Person>
|
||||
</maintainer>
|
||||
|
||||
</Project>
|
43
hooks/multi-pre-commit.hook
Executable file
43
hooks/multi-pre-commit.hook
Executable file
|
@ -0,0 +1,43 @@
|
|||
#!/bin/sh
|
||||
# Git pre-commit hook that runs multiple hooks specified in $HOOKS.
|
||||
# Make sure this script is executable. Bypass hooks with git commit --no-verify.
|
||||
|
||||
# This file is inspired by a set of unofficial pre-commit hooks available
|
||||
# at github.
|
||||
# Link: https://github.com/githubbrowser/Pre-commit-hooks
|
||||
# Contact: David Martin, david.martin.mailbox@googlemail.com
|
||||
|
||||
|
||||
###########################################################
|
||||
# SETTINGS:
|
||||
# pre-commit hooks to be executed. They should be in the same .git/hooks/ folder
|
||||
# as this script. Hooks should return 0 if successful and nonzero to cancel the
|
||||
# commit. They are executed in the order in which they are listed.
|
||||
###########################################################
|
||||
|
||||
HOOKS="hooks/pre-commit.hook hooks/pre-commit-python.hook"
|
||||
|
||||
# exit on error
|
||||
set -e
|
||||
|
||||
echo $PWD
|
||||
|
||||
for hook in $HOOKS
|
||||
do
|
||||
echo "Running hook: $hook"
|
||||
# run hook if it exists
|
||||
# if it returns with nonzero exit with 1 and thus abort the commit
|
||||
if [ -f "$PWD/$hook" ]; then
|
||||
"$PWD/$hook"
|
||||
if [ $? != 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Error: file $hook not found."
|
||||
echo "Aborting commit. Make sure the hook is at $PWD/$hook and executable."
|
||||
echo "You can disable it by removing it from the list"
|
||||
echo "You can skip all pre-commit hooks with --no-verify (not recommended)."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
81
hooks/pre-commit-python.hook
Executable file
81
hooks/pre-commit-python.hook
Executable file
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
NOT_PYCODESTYLE_COMPLIANT_MESSAGE_PRE = \
|
||||
"Your code is not fully pycodestyle compliant and contains"\
|
||||
" the following coding style issues:\n\n"
|
||||
|
||||
NOT_PYCODESTYLE_COMPLIANT_MESSAGE_POST = \
|
||||
"Please fix these errors and commit again, you can do so "\
|
||||
"from the root directory automatically like this, assuming the whole "\
|
||||
"file is to be commited:"
|
||||
|
||||
NO_PYCODESTYLE_MESSAGE = \
|
||||
"You should install the pycodestyle style checker to be able"\
|
||||
" to commit in this repo.\nIt allows us to garantee that "\
|
||||
"anything that is commited respects the pycodestyle coding style "\
|
||||
"standard.\nYou can install it:\n"\
|
||||
" * on ubuntu, debian: $sudo apt-get install pycodestyle \n"\
|
||||
" * on fedora: #yum install python3-pycodestyle \n"\
|
||||
" * on arch: #pacman -S python-pycodestyle \n"\
|
||||
" * or `pip install --user pycodestyle`"
|
||||
|
||||
|
||||
def system(*args, **kwargs):
|
||||
kwargs.setdefault('stdout', subprocess.PIPE)
|
||||
proc = subprocess.Popen(args, **kwargs)
|
||||
out, err = proc.communicate()
|
||||
if isinstance(out, bytes):
|
||||
out = out.decode()
|
||||
return out
|
||||
|
||||
|
||||
def copy_files_to_tmp_dir(files):
|
||||
tempdir = tempfile.mkdtemp()
|
||||
for name in files:
|
||||
filename = os.path.join(tempdir, name)
|
||||
filepath = os.path.dirname(filename)
|
||||
if not os.path.exists(filepath):
|
||||
os.makedirs(filepath)
|
||||
with open(filename, 'w') as f:
|
||||
system('git', 'show', ':' + name, stdout=f)
|
||||
|
||||
return tempdir
|
||||
|
||||
|
||||
def main():
|
||||
modified_files = system('git', 'diff-index', '--cached',
|
||||
'--name-only', 'HEAD', '--diff-filter=ACMR').split("\n")[:-1]
|
||||
non_compliant_files = []
|
||||
output_message = None
|
||||
|
||||
for modified_file in modified_files:
|
||||
try:
|
||||
if not modified_file.endswith(".py"):
|
||||
continue
|
||||
pycodestyle_errors = system('pycodestyle', '--repeat', '--ignore', 'E402,E501,E128,W605,W503', modified_file)
|
||||
if pycodestyle_errors:
|
||||
if output_message is None:
|
||||
output_message = NOT_PYCODESTYLE_COMPLIANT_MESSAGE_PRE
|
||||
output_message += pycodestyle_errors
|
||||
non_compliant_files.append(modified_file)
|
||||
except OSError as e:
|
||||
output_message = NO_PYCODESTYLE_MESSAGE
|
||||
break
|
||||
|
||||
if output_message:
|
||||
print(output_message)
|
||||
if non_compliant_files:
|
||||
print(NOT_PYCODESTYLE_COMPLIANT_MESSAGE_POST)
|
||||
for non_compliant_file in non_compliant_files:
|
||||
print("autopep8 -i ", non_compliant_file, "; git add ",
|
||||
non_compliant_file)
|
||||
print("git commit")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
83
hooks/pre-commit.hook
Executable file
83
hooks/pre-commit.hook
Executable file
|
@ -0,0 +1,83 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Check that the code follows a consistant code style
|
||||
#
|
||||
|
||||
# Check for existence of indent, and error out if not present.
|
||||
# On some *bsd systems the binary seems to be called gnunindent,
|
||||
# so check for that first.
|
||||
|
||||
version=`gnuindent --version 2>/dev/null`
|
||||
if test "x$version" = "x"; then
|
||||
version=`gindent --version 2>/dev/null`
|
||||
if test "x$version" = "x"; then
|
||||
version=`indent --version 2>/dev/null`
|
||||
if test "x$version" = "x"; then
|
||||
echo "GStreamer git pre-commit hook:"
|
||||
echo "Did not find GNU indent, please install it before continuing."
|
||||
exit 1
|
||||
else
|
||||
INDENT=indent
|
||||
fi
|
||||
else
|
||||
INDENT=gindent
|
||||
fi
|
||||
else
|
||||
INDENT=gnuindent
|
||||
fi
|
||||
|
||||
case `$INDENT --version` in
|
||||
GNU*)
|
||||
;;
|
||||
default)
|
||||
echo "GStreamer git pre-commit hook:"
|
||||
echo "Did not find GNU indent, please install it before continuing."
|
||||
echo "(Found $INDENT, but it doesn't seem to be GNU indent)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
INDENT_PARAMETERS="--braces-on-if-line \
|
||||
--case-brace-indentation0 \
|
||||
--case-indentation2 \
|
||||
--braces-after-struct-decl-line \
|
||||
--line-length80 \
|
||||
--no-tabs \
|
||||
--cuddle-else \
|
||||
--dont-line-up-parentheses \
|
||||
--continuation-indentation4 \
|
||||
--honour-newlines \
|
||||
--tab-size8 \
|
||||
--indent-level2 \
|
||||
--leave-preprocessor-space"
|
||||
|
||||
echo "--Checking style--"
|
||||
for file in `git diff-index --cached --name-only HEAD --diff-filter=ACMR| grep "\.c$"` ; do
|
||||
# nf is the temporary checkout. This makes sure we check against the
|
||||
# revision in the index (and not the checked out version).
|
||||
nf=`git checkout-index --temp ${file} | cut -f 1`
|
||||
newfile=`mktemp /tmp/${nf}.XXXXXX` || exit 1
|
||||
$INDENT ${INDENT_PARAMETERS} \
|
||||
$nf -o $newfile 2>> /dev/null
|
||||
# FIXME: Call indent twice as it tends to do line-breaks
|
||||
# different for every second call.
|
||||
$INDENT ${INDENT_PARAMETERS} \
|
||||
$newfile 2>> /dev/null
|
||||
diff -u -p "${nf}" "${newfile}"
|
||||
r=$?
|
||||
rm "${newfile}"
|
||||
rm "${nf}"
|
||||
if [ $r != 0 ] ; then
|
||||
echo "================================================================================================="
|
||||
echo " Code style error in: $file "
|
||||
echo " "
|
||||
echo " Please fix before committing. Don't forget to run git add before trying to commit again. "
|
||||
echo " If the whole file is to be committed, this should work (run from the top-level directory): "
|
||||
echo " "
|
||||
echo " gst-indent $file; git add $file; git commit"
|
||||
echo " "
|
||||
echo "================================================================================================="
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo "--Checking style pass--"
|
167
meson.build
Normal file
167
meson.build
Normal file
|
@ -0,0 +1,167 @@
|
|||
project('gst-devtools', 'c',
|
||||
version : '1.19.2',
|
||||
meson_version : '>= 0.54',
|
||||
default_options : [ 'warning_level=1',
|
||||
'c_std=gnu99',
|
||||
'buildtype=debugoptimized' ])
|
||||
|
||||
gst_version = meson.project_version()
|
||||
version_arr = gst_version.split('.')
|
||||
gst_version_major = version_arr[0].to_int()
|
||||
gst_version_minor = version_arr[1].to_int()
|
||||
gst_version_micro = version_arr[2].to_int()
|
||||
if gst_version_minor.is_even()
|
||||
TESTSUITE_VERSION = '@0@.@1@'.format(gst_version_major, gst_version_minor)
|
||||
else
|
||||
TESTSUITE_VERSION = 'master'
|
||||
endif
|
||||
|
||||
apiversion = '1.0'
|
||||
soversion = 0
|
||||
# maintaining compatibility with the previous libtool versioning
|
||||
# current = minor * 100 + micro
|
||||
curversion = gst_version_minor * 100 + gst_version_micro
|
||||
libversion = '@0@.@1@.0'.format(soversion, curversion)
|
||||
osxversion = curversion + 1
|
||||
|
||||
prefix = get_option('prefix')
|
||||
|
||||
glib_req = '>= 2.56.0'
|
||||
gst_req = '>= @0@.@1@.0'.format(gst_version_major, gst_version_minor)
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
|
||||
if cc.get_id() == 'msvc'
|
||||
msvc_args = [
|
||||
# Ignore several spurious warnings for things gstreamer does very commonly
|
||||
# If a warning is completely useless and spammy, use '/wdXXXX' to suppress it
|
||||
# If a warning is harmless but hard to fix, use '/woXXXX' so it's shown once
|
||||
# NOTE: Only add warnings here if you are sure they're spurious
|
||||
'/wd4018', # implicit signed/unsigned conversion
|
||||
'/wd4146', # unary minus on unsigned (beware INT_MIN)
|
||||
'/wd4244', # lossy type conversion (e.g. double -> int)
|
||||
'/wd4305', # truncating type conversion (e.g. double -> float)
|
||||
cc.get_supported_arguments(['/utf-8']), # set the input encoding to utf-8
|
||||
|
||||
# Enable some warnings on MSVC to match GCC/Clang behaviour
|
||||
'/w14062', # enumerator 'identifier' in switch of enum 'enumeration' is not handled
|
||||
'/w14101', # 'identifier' : unreferenced local variable
|
||||
'/w14189', # 'identifier' : local variable is initialized but not referenced
|
||||
]
|
||||
add_project_arguments(msvc_args, language: 'c')
|
||||
# Disable SAFESEH with MSVC for plugins and libs that use external deps that
|
||||
# are built with MinGW
|
||||
noseh_link_args = ['/SAFESEH:NO']
|
||||
else
|
||||
noseh_link_args = []
|
||||
endif
|
||||
|
||||
# Symbol visibility
|
||||
if cc.has_argument('-fvisibility=hidden')
|
||||
add_project_arguments('-fvisibility=hidden', language: 'c')
|
||||
endif
|
||||
|
||||
# Disable strict aliasing
|
||||
if cc.has_argument('-fno-strict-aliasing')
|
||||
add_project_arguments('-fno-strict-aliasing', language: 'c')
|
||||
endif
|
||||
|
||||
gst_dep = dependency('gstreamer-' + apiversion, version : gst_req,
|
||||
fallback : ['gstreamer', 'gst_dep'])
|
||||
gstbase_dep = dependency('gstreamer-base-' + apiversion, version : gst_req,
|
||||
fallback : ['gstreamer', 'gst_base_dep'])
|
||||
gst_pbutils_dep = dependency('gstreamer-pbutils-' + apiversion, version : gst_req,
|
||||
fallback : ['gst-plugins-base', 'pbutils_dep'])
|
||||
gst_video_dep = dependency('gstreamer-video-' + apiversion, version : gst_req,
|
||||
fallback : ['gst-plugins-base', 'video_dep'])
|
||||
gst_controller_dep = dependency('gstreamer-controller-' + apiversion, version : gst_req,
|
||||
fallback : ['gstreamer', 'gst_controller_dep'])
|
||||
gst_check_dep = dependency('gstreamer-check-1.0', version : gst_req,
|
||||
required : get_option('validate'),
|
||||
fallback : ['gstreamer', 'gst_check_dep'])
|
||||
|
||||
glib_dep = dependency('glib-2.0', version : '>=2.32.0',
|
||||
fallback: ['glib', 'libglib_dep'])
|
||||
gmodule_dep = dependency('gmodule-2.0',
|
||||
fallback: ['glib', 'libgmodule_dep'])
|
||||
gio_dep = dependency('gio-2.0',
|
||||
fallback: ['glib', 'libgio_dep'])
|
||||
|
||||
gtk_dep = dependency('gtk+-3.0', required: false)
|
||||
mathlib = cc.find_library('m', required : false)
|
||||
dl = cc.find_library('dl', required : false)
|
||||
json_dep = dependency('json-glib-1.0',
|
||||
fallback : ['json-glib', 'json_glib_dep'])
|
||||
|
||||
gst_c_args = ['-DHAVE_CONFIG_H', '-DGST_USE_UNSTABLE_API']
|
||||
|
||||
gir_init_section = [ '--add-init-section=extern void gst_init(gint*,gchar**);' + \
|
||||
'g_setenv("GST_REGISTRY_1.0", "/no/way/this/exists.reg", TRUE);' + \
|
||||
'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \
|
||||
'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \
|
||||
'gst_init(NULL,NULL);', '--quiet']
|
||||
gir = find_program('g-ir-scanner', required : get_option('introspection'))
|
||||
build_gir = gir.found() and (not meson.is_cross_build() or get_option('introspection').enabled())
|
||||
gnome = import('gnome')
|
||||
|
||||
if gst_dep.type_name() == 'internal'
|
||||
gst_debug_disabled = not subproject('gstreamer').get_variable('gst_debug')
|
||||
else
|
||||
# We can't check that in the case of subprojects as we won't
|
||||
# be able to build against an internal dependency (which is not built yet)
|
||||
gst_debug_disabled = cc.has_header_symbol('gst/gstconfig.h', 'GST_DISABLE_GST_DEBUG', dependencies: gst_dep)
|
||||
endif
|
||||
|
||||
if gst_debug_disabled and cc.has_argument('-Wno-unused')
|
||||
add_project_arguments('-Wno-unused', language: 'c')
|
||||
endif
|
||||
|
||||
warning_flags = [
|
||||
'-Wmissing-declarations',
|
||||
'-Wmissing-prototypes',
|
||||
'-Wredundant-decls',
|
||||
'-Wundef',
|
||||
'-Wwrite-strings',
|
||||
'-Wformat',
|
||||
'-Wformat-security',
|
||||
'-Winit-self',
|
||||
'-Wmissing-include-dirs',
|
||||
'-Waddress',
|
||||
'-Wno-multichar',
|
||||
'-Wdeclaration-after-statement',
|
||||
'-Wvla',
|
||||
'-Wpointer-arith',
|
||||
]
|
||||
|
||||
foreach extra_arg : warning_flags
|
||||
if cc.has_argument (extra_arg)
|
||||
add_project_arguments([extra_arg], language: 'c')
|
||||
endif
|
||||
endforeach
|
||||
|
||||
pkgconfig = import('pkgconfig')
|
||||
plugins_install_dir = join_paths(get_option('libdir'), 'gstreamer-1.0')
|
||||
plugins_pkgconfig_install_dir = join_paths(plugins_install_dir, 'pkgconfig')
|
||||
if get_option('default_library') == 'shared'
|
||||
# If we don't build static plugins there is no need to generate pc files
|
||||
plugins_pkgconfig_install_dir = disabler()
|
||||
endif
|
||||
pkgconfig_subdirs = ['gstreamer-1.0']
|
||||
|
||||
plugins_doc_dep = []
|
||||
plugins = []
|
||||
i18n = import('i18n')
|
||||
|
||||
python_mod = import('python')
|
||||
python3 = python_mod.find_installation()
|
||||
|
||||
if not get_option('validate').disabled()
|
||||
subdir('validate')
|
||||
endif
|
||||
|
||||
if not get_option('debug_viewer').disabled()
|
||||
subdir('debug-viewer')
|
||||
endif
|
||||
subdir('docs')
|
||||
|
||||
run_command(python3, '-c', 'import shutil; shutil.copy("hooks/multi-pre-commit.hook", ".git/hooks/pre-commit")')
|
12
meson_options.txt
Normal file
12
meson_options.txt
Normal file
|
@ -0,0 +1,12 @@
|
|||
option('validate', type : 'feature', value : 'auto',
|
||||
description : 'Build GstValidate')
|
||||
option('debug_viewer', type : 'feature', value : 'disabled',
|
||||
description : 'Build GstDebugViewer (GPLv3+)')
|
||||
option('introspection', type : 'feature', value : 'auto', yield : true,
|
||||
description : 'Generate gobject-introspection bindings')
|
||||
option('tests', type : 'feature', value : 'auto', yield : true,
|
||||
description : 'Build and enable unit tests')
|
||||
option('nls', type : 'feature', value : 'auto', yield: true,
|
||||
description : 'Enable native language support (translations)')
|
||||
option('doc', type : 'feature', value : 'auto', yield: true,
|
||||
description: 'Enable documentation.')
|
12
tracer/Makefile
Normal file
12
tracer/Makefile
Normal file
|
@ -0,0 +1,12 @@
|
|||
TEST_DATA = \
|
||||
logs/trace.latency.log
|
||||
|
||||
all:
|
||||
|
||||
logs/trace.latency.log:
|
||||
mkdir -p logs; \
|
||||
GST_DEBUG="GST_TRACER:7" GST_TRACERS=latency GST_DEBUG_FILE=$@ \
|
||||
gst-launch-1.0 -q audiotestsrc num-buffers=10 wave=silence ! audioconvert ! autoaudiosink
|
||||
|
||||
check: $(TEST_DATA)
|
||||
python3 -m unittest discover tracer "*_test.py"
|
117
tracer/README
Normal file
117
tracer/README
Normal file
|
@ -0,0 +1,117 @@
|
|||
# Add a python api for tracer analyzers
|
||||
|
||||
The python framework will parse the tracer log and aggregate information.
|
||||
the tool writer will subclass from the Analyzer class and override methods:
|
||||
|
||||
'handle_tracer_class(self, entry)'
|
||||
'handle_tracer_entry(self, entry)'
|
||||
|
||||
Each of those is optional. The entry field is the parsed log line. In most cases
|
||||
the tools will parse the structure contained in event[Parser.F_MESSAGE].
|
||||
|
||||
TODO: maybe do apply_tracer_entry() and revert_tracer_entry() - 'apply' will
|
||||
patch the shared state forward and 'revert' will 'apply' the inverse. This would
|
||||
let us go back from a state. An application should still take snapshots to allow
|
||||
for efficient jumping around. If that is the case we could also always go forward
|
||||
from a snapshot.
|
||||
|
||||
A tool will use an AnalysisRunner to chain one or more analyzers and iterate the
|
||||
log. A tool can also replay the log multiple times. If it does, it won't work in
|
||||
'streaming' mode though (streaming mode can offer live stats).
|
||||
|
||||
## TODO
|
||||
### gst shadow types
|
||||
Do we want to provide classes like GstBin, GstElement, GstPad, ... to aggregate
|
||||
info. One way to get them would be to have a GstLogAnalyzer that knows
|
||||
about data from the log tracer and populates the classes. Tools then can
|
||||
do e.g.
|
||||
|
||||
pad.name() # pad name
|
||||
pad.parent().name() # element name
|
||||
pad.peer().parent() # peer element
|
||||
pad.parent().state() # element state
|
||||
|
||||
This would allow us to e.g. get a pipeline graph at any point in the log.
|
||||
|
||||
### improve class handling
|
||||
We already parse the tracer classes. Add helpers that for numeric values that
|
||||
extract them, and aggregate min/max/avg. Consider other statistical information
|
||||
(std. deviation) and provide a rolling average for live view.
|
||||
|
||||
## Examples
|
||||
### Sequence chart generator (mscgen)
|
||||
|
||||
1.) Write file header
|
||||
|
||||
2.) collect element order
|
||||
Replay the log and use pad_link_pre to collect pad->peer_pad relationship.
|
||||
Build a sequence of element names and write to msc file.
|
||||
|
||||
3.) collect event processing
|
||||
Replay the log and use pad_push_event_pre to output message lines to mscfile.
|
||||
|
||||
4.) write footer and run the tool.
|
||||
|
||||
## Latency stats
|
||||
|
||||
1.) collect per sink-latencies and for each sink per source latencies
|
||||
Calculate min, max, avg. Consider streaming interface, where we update the stats
|
||||
e.g. once a sec
|
||||
|
||||
2.) in non-streaming mode write final statistic
|
||||
|
||||
## cpu load stats
|
||||
|
||||
Like latency stats, for cpu load. Process cpu load + per thread cpu load.
|
||||
|
||||
## top
|
||||
|
||||
Combine various stats tools into one.
|
||||
|
||||
|
||||
# todo
|
||||
## all tools
|
||||
* need some (optional) progress reporting
|
||||
|
||||
## structure parser
|
||||
* add an optional compiled regexp matcher an constructor param
|
||||
* then we'll parse the whole structure with a single regexp
|
||||
* this will only parse the top-level structure, we'd then check if there are
|
||||
nested substructure and handle them
|
||||
|
||||
# Improve tracers
|
||||
## log
|
||||
* the log tracer logs args and results into misc categories
|
||||
* issues
|
||||
* not easy/reliable to detect its output among other trace output
|
||||
* not easy to match pre/post lines
|
||||
* uses own do_log method, instead of gst_tracer_record_log
|
||||
* if we also log structures, we need to log the 'function' as the
|
||||
structure-name, also fields would be key=(type)val, instead of key=value
|
||||
* if we switch to gst_tracer_record_log, we'd need to register 27 formats :/
|
||||
## object ids
|
||||
When logging GstObjects in PTR_FORMAT, we log the name. Unfortunately the name
|
||||
is not neccesarilly unique over time. Same goes for the object address.
|
||||
When logging a tracer record we need a way for the scope fileds to uniquely
|
||||
relate to objects.
|
||||
a) parse object creation and destruction and build <name:id>-maps in the tracer
|
||||
tools:
|
||||
new-element message: gst_util_seqnum_next() and assoc with name
|
||||
<new stats>: get id by name and get data record via id
|
||||
|
||||
if we go this way, the stats tracer would log name in regullar record (which
|
||||
makes them more readable).
|
||||
|
||||
FIXME:
|
||||
- if we use stats or log and latency, do we log latency messages twice?
|
||||
grep -c ":: latency, " logs/trace.all.log
|
||||
8365
|
||||
|
||||
grep ":: event, " logs/trace.all.log | grep -c "name=(string)latency"
|
||||
63
|
||||
|
||||
seems to not happen, regardless of order in GST_TRACERS="latency;stats"
|
||||
|
||||
- why do we log element-ix for buffer, event, ... log-entries in the stats
|
||||
tracer? We log new-pad, when the pad get added to a parent, so we should know
|
||||
the element already
|
217
tracer/gsttr-stats.py
Normal file
217
tracer/gsttr-stats.py
Normal file
|
@ -0,0 +1,217 @@
|
|||
#!/usr/bin/env python3
|
||||
'''
|
||||
Aggregate values for each tracer event type and print them with some statistics.
|
||||
|
||||
How to run:
|
||||
1) generate some log
|
||||
GST_DEBUG="GST_TRACER:7" GST_TRACERS="stats;rusage;latency" GST_DEBUG_FILE=trace.log <application>
|
||||
|
||||
2) print everything
|
||||
python3 gsttr-stats.py trace.log
|
||||
|
||||
3) print selected entries only
|
||||
python3 gsttr-stats.py -c latency trace.log
|
||||
'''
|
||||
# TODO:
|
||||
# more options
|
||||
# - live-update interval (for file=='-')
|
||||
#
|
||||
# - for values like timestamps, we only want min/max but no average
|
||||
|
||||
import logging
|
||||
from fnmatch import fnmatch
|
||||
from tracer.analysis_runner import AnalysisRunner
|
||||
from tracer.analyzer import Analyzer
|
||||
from tracer.parser import Parser
|
||||
from tracer.structure import Structure
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logger = logging.getLogger('gsttr-stats')
|
||||
|
||||
_SCOPE_RELATED_TO = {
|
||||
'GST_TRACER_VALUE_SCOPE_PAD': 'Pad',
|
||||
'GST_TRACER_VALUE_SCOPE_ELEMENT': 'Element',
|
||||
'GST_TRACER_VALUE_SCOPE_THREAD': 'Thread',
|
||||
'GST_TRACER_VALUE_SCOPE_PROCESS': 'Process',
|
||||
}
|
||||
|
||||
_NUMERIC_TYPES = ('int', 'uint', 'gint', 'guint', 'gint64', 'guint64')
|
||||
|
||||
|
||||
class Stats(Analyzer):
|
||||
|
||||
def __init__(self, classes):
|
||||
super(Stats, self).__init__()
|
||||
self.classes = classes
|
||||
self.records = {}
|
||||
self.data = {}
|
||||
|
||||
def handle_tracer_class(self, event):
|
||||
s = Structure(event[Parser.F_MESSAGE])
|
||||
# TODO only for debugging
|
||||
# print("tracer class:", repr(s))
|
||||
name = s.name[:-len('.class')]
|
||||
record = {
|
||||
'class': s,
|
||||
'scope': {},
|
||||
'value': {},
|
||||
}
|
||||
self.records[name] = record
|
||||
for k, v in s.values.items():
|
||||
if v.name == 'scope':
|
||||
# TODO only for debugging
|
||||
# print("scope: [%s]=%s" % (k, v))
|
||||
record['scope'][k] = v
|
||||
elif v.name == 'value':
|
||||
# skip non numeric and those without min/max
|
||||
if v.values['type'] in _NUMERIC_TYPES and 'min' in v.values and 'max' in v.values:
|
||||
# TODO only for debugging
|
||||
# print("value: [%s]=%s" % (k, v))
|
||||
record['value'][k] = v
|
||||
# else:
|
||||
# TODO only for debugging
|
||||
# print("skipping value: [%s]=%s" % (k, v))
|
||||
|
||||
def handle_tracer_entry(self, event):
|
||||
# use first field in message (structure-id) if none
|
||||
if event[Parser.F_FUNCTION]:
|
||||
return
|
||||
|
||||
msg = event[Parser.F_MESSAGE]
|
||||
p = msg.find(',')
|
||||
if p == -1:
|
||||
return
|
||||
|
||||
entry_name = msg[:p]
|
||||
if self.classes:
|
||||
if not any([fnmatch(entry_name, c) for c in self.classes]):
|
||||
return
|
||||
|
||||
record = self.records.get(entry_name)
|
||||
if not record:
|
||||
return
|
||||
|
||||
try:
|
||||
s = Structure(msg)
|
||||
except ValueError:
|
||||
logger.warning("failed to parse: '%s'", msg)
|
||||
return
|
||||
|
||||
# aggregate event based on class
|
||||
for sk, sv in record['scope'].items():
|
||||
# look up bin by scope (or create new)
|
||||
key = (_SCOPE_RELATED_TO[sv.values['related-to']] + ":" + str(s.values[sk]))
|
||||
scope = self.data.get(key)
|
||||
if not scope:
|
||||
scope = {}
|
||||
self.data[key] = scope
|
||||
for vk, vv in record['value'].items():
|
||||
# skip optional fields
|
||||
if vk not in s.values:
|
||||
continue
|
||||
if not s.values.get('have-' + vk, True):
|
||||
continue
|
||||
|
||||
key = entry_name + "/" + vk
|
||||
data = scope.get(key)
|
||||
if not data:
|
||||
data = {'num': 0}
|
||||
if '_FLAGS_AGGREGATED' not in vv.values.get('flags', ''):
|
||||
data['sum'] = 0
|
||||
if 'max' in vv.values and 'min' in vv.values:
|
||||
data['min'] = int(vv.values['max'])
|
||||
data['max'] = int(vv.values['min'])
|
||||
else:
|
||||
# aggregated: don't average, collect first value
|
||||
data['min'] = int(s.values[vk])
|
||||
scope[key] = data
|
||||
# update min/max/sum and count via value
|
||||
dv = int(s.values[vk])
|
||||
data['num'] += 1
|
||||
if 'sum' in data:
|
||||
data['sum'] += dv
|
||||
if 'min' in data:
|
||||
data['min'] = min(dv, data['min'])
|
||||
if 'max' in data:
|
||||
data['max'] = max(dv, data['max'])
|
||||
else:
|
||||
# aggregated: collect last value
|
||||
data['max'] = dv
|
||||
|
||||
def report(self):
|
||||
# headline
|
||||
print("%-45s: %30s: %16s/%16s/%16s" % (
|
||||
'scope', 'value', 'min', 'avg', 'max'))
|
||||
# iterate scopes
|
||||
for sk, sv in self.data.items():
|
||||
# iterate tracers
|
||||
for tk, tv in sv.items():
|
||||
mi = tv.get('min', '-')
|
||||
ma = tv.get('max', '-')
|
||||
if 'sum' in tv:
|
||||
avg = tv['sum'] / tv['num']
|
||||
else:
|
||||
avg = '-'
|
||||
if mi == ma:
|
||||
mi = ma = '-'
|
||||
if is_time_field(tk):
|
||||
if mi != '-':
|
||||
mi = format_ts(mi)
|
||||
if ma != '-':
|
||||
ma = format_ts(ma)
|
||||
if avg != '-':
|
||||
avg = format_ts(avg)
|
||||
print("%-45s: %30s: %16s/%16s/%16s" % (sk, tk, mi, avg, ma))
|
||||
|
||||
|
||||
class ListClasses(Analyzer):
|
||||
|
||||
def __init__(self):
|
||||
super(ListClasses, self).__init__()
|
||||
|
||||
def handle_tracer_class(self, event):
|
||||
s = Structure(event[Parser.F_MESSAGE])
|
||||
print(s.name)
|
||||
|
||||
def handle_tracer_entry(self, event):
|
||||
raise StopIteration
|
||||
|
||||
|
||||
def format_ts(ts):
|
||||
sec = 1e9
|
||||
h = int(ts // (sec * 60 * 60))
|
||||
m = int((ts // (sec * 60)) % 60)
|
||||
s = (ts / sec)
|
||||
return '{:02d}:{:02d}:{:010.7f}'.format(h, m, s)
|
||||
|
||||
|
||||
def is_time_field(f):
|
||||
# TODO: need proper units
|
||||
return (f.endswith('/time') or f.endswith('-dts') or f.endswith('-pts')
|
||||
or f.endswith('-duration'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('file', nargs='?', default='debug.log')
|
||||
parser.add_argument('-c', '--class', action='append', dest='classes',
|
||||
help='tracer class selector (default: all)')
|
||||
parser.add_argument('-l', '--list-classes', action='store_true',
|
||||
help='show tracer classes')
|
||||
args = parser.parse_args()
|
||||
|
||||
analyzer = None
|
||||
if args.list_classes:
|
||||
analyzer = ListClasses()
|
||||
else:
|
||||
analyzer = stats = Stats(args.classes)
|
||||
|
||||
with Parser(args.file) as log:
|
||||
runner = AnalysisRunner(log)
|
||||
runner.add_analyzer(analyzer)
|
||||
runner.run()
|
||||
|
||||
if not args.list_classes:
|
||||
stats.report()
|
287
tracer/gsttr-tsplot.py
Normal file
287
tracer/gsttr-tsplot.py
Normal file
|
@ -0,0 +1,287 @@
|
|||
#!/usr/bin/env python3
|
||||
'''
|
||||
Plot buffer pts and events in relation to the wall clock. The plots can be used
|
||||
to spot anomalies, such as processing gaps.
|
||||
|
||||
How to run:
|
||||
1) generate a log
|
||||
GST_DEBUG="GST_TRACER:7" GST_TRACERS=stats GST_DEBUG_FILE=trace.log <application>
|
||||
|
||||
2) generate the images
|
||||
python3 gsttr-tsplot.py trace.log <outdir>
|
||||
eog <outdir>/*.png
|
||||
'''
|
||||
|
||||
# TODO:
|
||||
# - improve event plot
|
||||
# - ideally each event is a vertical line
|
||||
# http://stackoverflow.com/questions/35105672/vertical-lines-from-data-in-file-in-time-series-plot-using-gnuplot
|
||||
# - this won't work well if the event is e.g. 'qos'
|
||||
# - we could sort them by event type and separate them by double new-lines,
|
||||
# we'd then use 'index <x>' to plot them in different colors with
|
||||
# - buffer-pts should be ahead of clock time of the pipeline
|
||||
# - we don't have the clock ts in the log though
|
||||
|
||||
import logging
|
||||
import os
|
||||
from subprocess import Popen, PIPE, DEVNULL
|
||||
from string import Template
|
||||
from tracer.analysis_runner import AnalysisRunner
|
||||
from tracer.analyzer import Analyzer
|
||||
from tracer.parser import Parser
|
||||
from tracer.structure import Structure
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
logger = logging.getLogger('gsttr-tsplot')
|
||||
|
||||
_HANDLED_CLASSES = ('buffer', 'event', 'new-pad', 'new-element')
|
||||
|
||||
_GST_BUFFER_FLAG_DISCONT = (1 << 6)
|
||||
|
||||
_PLOT_SCRIPT_HEAD = Template(
|
||||
'''
|
||||
set term pngcairo truecolor size $width,$height font "Helvetica,14"
|
||||
set style line 1 lc rgb '#8b1a0e' pt 1 ps 1 lt 1 lw 1 # --- red
|
||||
set style line 2 lc rgb '#5e9c36' pt 6 ps 1 lt 1 lw 1 # --- green
|
||||
set style line 100 lc rgb '#999999' lt 0 lw 1
|
||||
set grid back ls 100
|
||||
set key font ",10"
|
||||
set label font ",10"
|
||||
set tics font ",10"
|
||||
set xlabel font ",10"
|
||||
set ylabel font ",10"
|
||||
''')
|
||||
_PLOT_SCRIPT_BODY = Template(
|
||||
'''
|
||||
set output '$png_file_name'
|
||||
set multiplot layout 3,1 title "$title\\n$subtitle"
|
||||
|
||||
set xlabel ""
|
||||
set xrange [*:*] writeback
|
||||
set xtics format ""
|
||||
set ylabel "Buffer Time (sec.msec)" offset 1,0
|
||||
set yrange [*:*]
|
||||
set ytics
|
||||
plot '$buf_file_name' using 1:2 with linespoints ls 1 notitle
|
||||
|
||||
set xrange restore
|
||||
set ylabel "Duration (sec.msec)" offset 1,0
|
||||
plot '$buf_file_name' using 1:3 with linespoints ls 1title "cycle", \
|
||||
'' using 1:4 with linespoints ls 2 title "duration"
|
||||
|
||||
set xrange restore
|
||||
set xtics format "%g" scale .5 offset 0,.5
|
||||
set xlabel "Clock Time (sec.msec)" offset 0,1
|
||||
set ylabel "Events" offset 1,0
|
||||
set yrange [$ypos_max:10]
|
||||
set ytics format ""
|
||||
plot '$ev_file_name' using 1:4:3:(0) with vectors heads size screen 0.008,90 ls 1 notitle, \
|
||||
'' using 2:4 with points ls 1 notitle, \
|
||||
'' using 2:4:5 with labels font ',7' offset char 0,-0.5 notitle
|
||||
unset multiplot
|
||||
''')
|
||||
|
||||
|
||||
class TsPlot(Analyzer):
|
||||
'''Generate a timestamp plots from a tracer log.
|
||||
|
||||
These show the buffer pts on the y-axis and the wall-clock time the buffer
|
||||
was produced on the x-axis. This helps to spot timing issues, such as
|
||||
stalled elements.
|
||||
'''
|
||||
|
||||
def __init__(self, outdir, show_ghost_pads, size):
|
||||
super(TsPlot, self).__init__()
|
||||
self.outdir = outdir
|
||||
self.show_ghost_pads = show_ghost_pads
|
||||
self.params = {
|
||||
'width': size[0],
|
||||
'height': size[1],
|
||||
}
|
||||
self.buf_files = {}
|
||||
self.buf_cts = {}
|
||||
self.ev_files = {}
|
||||
self.element_names = {}
|
||||
self.element_info = {}
|
||||
self.pad_names = {}
|
||||
self.pad_info = {}
|
||||
self.ev_labels = {}
|
||||
self.ev_data = {}
|
||||
self.ev_ypos = {}
|
||||
|
||||
def _get_data_file(self, files, key, name_template):
|
||||
data_file = files.get(key)
|
||||
if not data_file:
|
||||
pad_name = self.pad_names.get(key)
|
||||
if pad_name:
|
||||
file_name = name_template % (self.outdir, key, pad_name)
|
||||
data_file = open(file_name, 'w')
|
||||
files[key] = data_file
|
||||
return data_file
|
||||
|
||||
def _log_event_data(self, pad_file, ix):
|
||||
data = self.ev_data.get(ix)
|
||||
if not data:
|
||||
return
|
||||
line = self.ev_labels[ix]
|
||||
ct = data['ct']
|
||||
x1 = data['first-ts']
|
||||
# TODO: scale 'y' according to max-y of buf or do a multiplot
|
||||
y = (1 + data['ypos']) * -10
|
||||
if ct == 1:
|
||||
pad_file.write('%f %f %f %f "%s"\n' % (x1, x1, 0.0, y, line))
|
||||
else:
|
||||
x2 = data['last-ts']
|
||||
xd = (x2 - x1)
|
||||
xm = x1 + xd / 2
|
||||
pad_file.write('%f %f %f %f "%s (%d)"\n' % (x1, xm, xd, y, line, ct))
|
||||
|
||||
def _log_event(self, s):
|
||||
# build a [ts, event-name] data file
|
||||
ix = int(s.values['pad-ix'])
|
||||
pad_file = self._get_data_file(self.ev_files, ix, '%s/ev_%d_%s.dat')
|
||||
if not pad_file:
|
||||
return
|
||||
# convert timestamps to seconds
|
||||
x = int(s.values['ts']) / 1e9
|
||||
# some events fire often, labeling each would be unreadable
|
||||
# so we aggregate a series of events of the same type
|
||||
line = s.values['name']
|
||||
if line == self.ev_labels.get(ix):
|
||||
# count lines and track last ts
|
||||
data = self.ev_data[ix]
|
||||
data['ct'] += 1
|
||||
data['last-ts'] = x
|
||||
else:
|
||||
self._log_event_data(pad_file, ix)
|
||||
# start new data, assign a -y coord by event type
|
||||
if ix not in self.ev_ypos:
|
||||
ypos = {}
|
||||
self.ev_ypos[ix] = ypos
|
||||
else:
|
||||
ypos = self.ev_ypos[ix]
|
||||
if line in ypos:
|
||||
y = ypos[line]
|
||||
else:
|
||||
y = len(ypos)
|
||||
ypos[line] = y
|
||||
self.ev_labels[ix] = line
|
||||
self.ev_data[ix] = {
|
||||
'ct': 1,
|
||||
'first-ts': x,
|
||||
'ypos': y,
|
||||
}
|
||||
|
||||
def _log_buffer(self, s):
|
||||
if not int(s.values['have-buffer-pts']):
|
||||
return
|
||||
# build a [ts, buffer-pts] data file
|
||||
ix = int(s.values['pad-ix'])
|
||||
pad_file = self._get_data_file(self.buf_files, ix, '%s/buf_%d_%s.dat')
|
||||
if not pad_file:
|
||||
return
|
||||
flags = int(s.values['buffer-flags'])
|
||||
if flags & _GST_BUFFER_FLAG_DISCONT:
|
||||
pad_file.write('\n')
|
||||
# convert timestamps to e.g. seconds
|
||||
cts = int(s.values['ts']) / 1e9
|
||||
pts = int(s.values['buffer-pts']) / 1e9
|
||||
dur = int(s.values['buffer-duration']) / 1e9
|
||||
if ix not in self.buf_cts:
|
||||
dcts = 0
|
||||
else:
|
||||
dcts = cts - self.buf_cts[ix]
|
||||
self.buf_cts[ix] = cts
|
||||
pad_file.write('%f %f %f %f\n' % (cts, pts, dcts, dur))
|
||||
|
||||
def handle_tracer_entry(self, event):
|
||||
if event[Parser.F_FUNCTION]:
|
||||
return
|
||||
|
||||
msg = event[Parser.F_MESSAGE]
|
||||
p = msg.find(',')
|
||||
if p == -1:
|
||||
return
|
||||
entry_name = msg[:p]
|
||||
if entry_name not in _HANDLED_CLASSES:
|
||||
return
|
||||
|
||||
try:
|
||||
s = Structure(msg)
|
||||
except ValueError:
|
||||
logger.warning("failed to parse: '%s'", msg)
|
||||
return
|
||||
|
||||
if entry_name == 'new-element':
|
||||
ix = int(s.values['ix'])
|
||||
self.element_names[ix] = s.values['name']
|
||||
self.element_info[ix] = 'Element Type: %s' % s.values['type']
|
||||
elif entry_name == 'new-pad':
|
||||
pad_type = s.values['type']
|
||||
if self.show_ghost_pads or pad_type not in ['GstGhostPad', 'GstProxyPad']:
|
||||
parent_ix = int(s.values['parent-ix'])
|
||||
parent_name = self.element_names.get(parent_ix, '')
|
||||
ix = int(s.values['ix'])
|
||||
self.pad_names[ix] = '%s.%s' % (parent_name, s.values['name'])
|
||||
self.pad_info[ix] = '(%s, Pad Type: %s)' % (
|
||||
self.element_info.get(parent_ix, ''), pad_type)
|
||||
elif entry_name == 'event':
|
||||
self._log_event(s)
|
||||
else: # 'buffer'
|
||||
self._log_buffer(s)
|
||||
|
||||
def report(self):
|
||||
for ix, pad_file in self.ev_files.items():
|
||||
self._log_event_data(pad_file, ix)
|
||||
pad_file.close()
|
||||
|
||||
script = _PLOT_SCRIPT_HEAD.substitute(self.params)
|
||||
for ix, pad_file in self.buf_files.items():
|
||||
pad_file.close()
|
||||
name = self.pad_names[ix]
|
||||
buf_file_name = '%s/buf_%d_%s.dat' % (self.outdir, ix, name)
|
||||
ev_file_name = '%s/ev_%d_%s.dat' % (self.outdir, ix, name)
|
||||
png_file_name = '%s/%d_%s.png' % (self.outdir, ix, name)
|
||||
sub_title = self.pad_info[ix]
|
||||
ypos_max = (2 + len(self.ev_ypos[ix])) * -10
|
||||
script += _PLOT_SCRIPT_BODY.substitute(self.params, title=name,
|
||||
subtitle=sub_title, buf_file_name=buf_file_name,
|
||||
ev_file_name=ev_file_name, png_file_name=png_file_name,
|
||||
ypos_max=ypos_max)
|
||||
# plot PNGs
|
||||
p = Popen(['gnuplot'], stdout=DEVNULL, stdin=PIPE)
|
||||
p.communicate(input=script.encode('utf-8'))
|
||||
|
||||
# cleanup
|
||||
for ix, pad_file in self.buf_files.items():
|
||||
name = self.pad_names[ix]
|
||||
buf_file_name = '%s/buf_%d_%s.dat' % (self.outdir, ix, name)
|
||||
os.unlink(buf_file_name)
|
||||
for ix, pad_file in self.ev_files.items():
|
||||
name = self.pad_names[ix]
|
||||
ev_file_name = '%s/ev_%d_%s.dat' % (self.outdir, ix, name)
|
||||
os.unlink(ev_file_name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('file', nargs='?', default='debug.log')
|
||||
parser.add_argument('outdir', nargs='?', default='tsplot')
|
||||
parser.add_argument('-g', '--ghost-pads', action='store_true',
|
||||
help='also plot data for ghost-pads')
|
||||
parser.add_argument('-s', '--size', action='store', default='1600x600',
|
||||
help='graph size as WxH')
|
||||
args = parser.parse_args()
|
||||
|
||||
os.makedirs(args.outdir, exist_ok=True)
|
||||
size = [int(s) for s in args.size.split('x')]
|
||||
|
||||
with Parser(args.file) as log:
|
||||
tsplot = TsPlot(args.outdir, args.ghost_pads, size)
|
||||
runner = AnalysisRunner(log)
|
||||
runner.add_analyzer(tsplot)
|
||||
runner.run()
|
||||
|
||||
tsplot.report()
|
48
tracer/tracer/analysis_runner.py
Normal file
48
tracer/tracer/analysis_runner.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
try:
|
||||
from tracer.parser import Parser
|
||||
except BaseException:
|
||||
from parser import Parser
|
||||
|
||||
|
||||
class AnalysisRunner(object):
|
||||
"""
|
||||
Runs several Analyzers over a log.
|
||||
|
||||
Iterates log using a Parser and dispatches to a set of analyzers.
|
||||
"""
|
||||
|
||||
def __init__(self, log):
|
||||
self.log = log
|
||||
self.analyzers = []
|
||||
|
||||
def add_analyzer(self, analyzer):
|
||||
self.analyzers.append(analyzer)
|
||||
|
||||
def handle_tracer_class(self, event):
|
||||
for analyzer in self.analyzers:
|
||||
analyzer.handle_tracer_class(event)
|
||||
|
||||
def handle_tracer_entry(self, event):
|
||||
for analyzer in self.analyzers:
|
||||
analyzer.handle_tracer_entry(event)
|
||||
|
||||
def is_tracer_class(self, event):
|
||||
return (event[Parser.F_FILENAME] == 'gsttracerrecord.c'
|
||||
and event[Parser.F_CATEGORY] == 'GST_TRACER'
|
||||
and '.class' in event[Parser.F_MESSAGE])
|
||||
|
||||
def is_tracer_entry(self, event):
|
||||
return (not event[Parser.F_LINE] and not event[Parser.F_FILENAME])
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
for event in self.log:
|
||||
# check if it is a tracer.class or tracer event
|
||||
if self.is_tracer_entry(event):
|
||||
self.handle_tracer_entry(event)
|
||||
elif self.is_tracer_class(event):
|
||||
self.handle_tracer_class(event)
|
||||
# else:
|
||||
# print("unhandled:", repr(event))
|
||||
except StopIteration:
|
||||
pass
|
26
tracer/tracer/analysis_runner_test.py
Normal file
26
tracer/tracer/analysis_runner_test.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
import unittest
|
||||
|
||||
from tracer.analysis_runner import AnalysisRunner
|
||||
|
||||
TRACER_CLASS = (
|
||||
'0:00:00.036373170', 1788, '0x23bca70', 'TRACE', 'GST_TRACER',
|
||||
'gsttracerrecord.c', 110, 'gst_tracer_record_build_format', None,
|
||||
r'latency.class, src=(structure)"scope\\,\\ type\\=\\(type\\)gchararray\\,\\ related-to\\=\\(GstTracerValueScope\\)GST_TRACER_VALUE_SCOPE_PAD\\;", sink=(structure)"scope\\,\\ type\\=\\(type\\)gchararray\\,\\ related-to\\=\\(GstTracerValueScope\\)GST_TRACER_VALUE_SCOPE_PAD\\;", time=(structure)"value\\,\\ type\\=\\(type\\)guint64\\,\\ description\\=\\(string\\)\\"time\\\\\\ it\\\\\\ took\\\\\\ for\\\\\\ the\\\\\\ buffer\\\\\\ to\\\\\\ go\\\\\\ from\\\\\\ src\\\\\\ to\\\\\\ sink\\\\\\ ns\\"\\,\\ flags\\=\\(GstTracerValueFlags\\)GST_TRACER_VALUE_FLAGS_AGGREGATED\\,\\ min\\=\\(guint64\\)0\\,\\ max\\=\\(guint64\\)18446744073709551615\\;";'
|
||||
)
|
||||
|
||||
TRACER_ENTRY = (
|
||||
'0:00:00.142391137', 1788, '0x7f8a201056d0', 'TRACE', 'GST_TRACER',
|
||||
'', 0, '', None,
|
||||
r'latency, src=(string)source_src, sink=(string)pulsesink0_sink, time=(guint64)47091349;'
|
||||
)
|
||||
|
||||
|
||||
class TestAnalysisRunner(unittest.TestCase):
|
||||
|
||||
def test_detect_tracer_class(self):
|
||||
a = AnalysisRunner(None)
|
||||
self.assertTrue(a.is_tracer_class(TRACER_CLASS))
|
||||
|
||||
def test_detect_tracer_entry(self):
|
||||
a = AnalysisRunner(None)
|
||||
self.assertTrue(a.is_tracer_entry(TRACER_ENTRY))
|
15
tracer/tracer/analyzer.py
Normal file
15
tracer/tracer/analyzer.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
class Analyzer(object):
|
||||
"""
|
||||
Base class for a gst tracer analyzer.
|
||||
|
||||
Will be used in conjunction with a AnalysisRunner.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def handle_tracer_class(self, event):
|
||||
pass
|
||||
|
||||
def handle_tracer_entry(self, event):
|
||||
pass
|
82
tracer/tracer/parser.py
Normal file
82
tracer/tracer/parser.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
def _log_line_regex():
|
||||
|
||||
# "0:00:00.777913000 "
|
||||
TIME = r"(\d+:\d\d:\d\d\.\d+)\s+"
|
||||
# "DEBUG "
|
||||
# LEVEL = "([A-Z]+)\s+"
|
||||
LEVEL = "(TRACE)\s+"
|
||||
# "0x8165430 "
|
||||
THREAD = r"(0x[0-9a-f]+)\s+"
|
||||
# "GST_REFCOUNTING ", "flacdec "
|
||||
CATEGORY = "([A-Za-z0-9_-]+)\s+"
|
||||
# " 3089 "
|
||||
PID = r"(\d+)\s*"
|
||||
FILENAME = r"([^:]*):"
|
||||
LINE = r"(\d+):"
|
||||
FUNCTION = r"([A-Za-z0-9_]*):"
|
||||
# FIXME: When non-g(st)object stuff is logged with *_OBJECT (like
|
||||
# buffers!), the address is printed *without* <> brackets!
|
||||
OBJECT = "(?:<([^>]+)>)?"
|
||||
MESSAGE = "(.+)"
|
||||
|
||||
ANSI = "(?:\x1b\\[[0-9;]*m\\s*)*\\s*"
|
||||
|
||||
return [TIME, ANSI, PID, ANSI, THREAD, ANSI, LEVEL, ANSI, CATEGORY,
|
||||
FILENAME, LINE, FUNCTION, ANSI, OBJECT, ANSI, MESSAGE]
|
||||
|
||||
|
||||
class Parser(object):
|
||||
"""
|
||||
Helper to parse a tracer log.
|
||||
|
||||
Implements context manager and iterator.
|
||||
"""
|
||||
|
||||
# record fields
|
||||
F_TIME = 0
|
||||
F_PID = 1
|
||||
F_THREAD = 2
|
||||
F_LEVEL = 3
|
||||
F_CATEGORY = 4
|
||||
F_FILENAME = 5
|
||||
F_LINE = 6
|
||||
F_FUNCTION = 7
|
||||
F_OBJECT = 8
|
||||
F_MESSAGE = 9
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.log_regex = re.compile(''.join(_log_line_regex()))
|
||||
self.file = None
|
||||
|
||||
def __enter__(self):
|
||||
if self.filename != '-':
|
||||
self.file = open(self.filename, 'rt')
|
||||
else:
|
||||
self.file = sys.stdin
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
if self.filename != '-':
|
||||
self.file.close()
|
||||
self.file = None
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
log_regex = self.log_regex
|
||||
data = self.file
|
||||
while True:
|
||||
line = next(data)
|
||||
match = log_regex.match(line)
|
||||
if match:
|
||||
g = list(match.groups())
|
||||
g[Parser.F_PID] = int(g[Parser.F_PID])
|
||||
g[Parser.F_LINE] = int(g[Parser.F_LINE])
|
||||
return g
|
13
tracer/tracer/parser_perf.py
Normal file
13
tracer/tracer/parser_perf.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
from analysis_runner import AnalysisRunner
|
||||
from parser import Parser
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('file', nargs='?', default='debug.log')
|
||||
args = parser.parse_args()
|
||||
|
||||
with Parser(args.file) as log:
|
||||
runner = AnalysisRunner(log)
|
||||
runner.run()
|
47
tracer/tracer/parser_test.py
Normal file
47
tracer/tracer/parser_test.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
import sys
|
||||
import unittest
|
||||
|
||||
from tracer.parser import Parser
|
||||
|
||||
TESTFILE = './logs/trace.latency.log'
|
||||
|
||||
TEXT_DATA = ['first line', 'second line']
|
||||
|
||||
TRACER_LOG_DATA = [
|
||||
'0:00:00.079422574 7664 0x238ac70 TRACE GST_TRACER :0:: thread-rusage, thread-id=(guint64)37268592, ts=(guint64)79416000, average-cpuload=(uint)1000, current-cpuload=(uint)1000, time=(guint64)79418045;'
|
||||
]
|
||||
TRACER_CLASS_LOG_DATA = [
|
||||
'0:00:00.041536066 1788 0x14b2150 TRACE GST_TRACER gsttracerrecord.c:110:gst_tracer_record_build_format: latency.class, src=(structure)"scope\,\ type\=\(type\)gchararray\,\ related-to\=\(GstTracerValueScope\)GST_TRACER_VALUE_SCOPE_PAD\;", sink=(structure)"scope\,\ type\=\(type\)gchararray\,\ related-to\=\(GstTracerValueScope\)GST_TRACER_VALUE_SCOPE_PAD\;", time=(structure)"value\,\ type\=\(type\)guint64\,\ description\=\(string\)\"time\\\ it\\\ took\\\ for\\\ the\\\ buffer\\\ to\\\ go\\\ from\\\ src\\\ to\\\ sink\\\ ns\"\,\ flags\=\(GstTracerValueFlags\)GST_TRACER_VALUE_FLAGS_AGGREGATED\,\ min\=\(guint64\)0\,\ max\=\(guint64\)18446744073709551615\;";'
|
||||
]
|
||||
|
||||
|
||||
class TestParser(unittest.TestCase):
|
||||
|
||||
def test___init__(self):
|
||||
log = Parser(TESTFILE)
|
||||
self.assertIsNone(log.file)
|
||||
|
||||
def test___enter___with_file(self):
|
||||
with Parser(TESTFILE) as log:
|
||||
self.assertIsNotNone(log.file)
|
||||
|
||||
def test___enter___with_stdin(self):
|
||||
sys.stdin = iter(TEXT_DATA)
|
||||
with Parser('-') as log:
|
||||
self.assertIsNotNone(log.file)
|
||||
|
||||
def test_random_text_reports_none(self):
|
||||
sys.stdin = iter(TEXT_DATA)
|
||||
with Parser('-') as log:
|
||||
with self.assertRaises(StopIteration):
|
||||
next(log)
|
||||
|
||||
def test_log_file_reports_trace_log(self):
|
||||
with Parser(TESTFILE) as log:
|
||||
self.assertIsNotNone(next(log))
|
||||
|
||||
def test_trace_log_parsed(self):
|
||||
sys.stdin = iter(TRACER_LOG_DATA)
|
||||
with Parser('-') as log:
|
||||
event = next(log)
|
||||
self.assertEqual(len(event), 10)
|
99
tracer/tracer/structure.py
Normal file
99
tracer/tracer/structure.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
logger = logging.getLogger('structure')
|
||||
|
||||
UNESCAPE = re.compile(r'(?<!\\)\\(.)')
|
||||
|
||||
INT_TYPES = "".join(
|
||||
("int", "uint", "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64")
|
||||
)
|
||||
|
||||
|
||||
class Structure(object):
|
||||
"""
|
||||
Gst Structure parser.
|
||||
|
||||
Has publicly accesible members representing the structure data:
|
||||
name -- the structure name
|
||||
types -- a dictionary keyed by the field name
|
||||
values -- a dictionary keyed by the field name
|
||||
"""
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
self.name, self.types, self.values = Structure._parse(text)
|
||||
|
||||
def __repr__(self):
|
||||
return self.text
|
||||
|
||||
@staticmethod
|
||||
def _find_eos(s):
|
||||
# find next '"' without preceeding '\'
|
||||
i = 0
|
||||
# logger.debug("find_eos: '%s'", s)
|
||||
while True: # faster than regexp for '[^\\]\"'
|
||||
p = s.index('"')
|
||||
i += p + 1
|
||||
if s[p - 1] != '\\':
|
||||
# logger.debug("... ok : '%s'", s[p:])
|
||||
return i
|
||||
s = s[(p + 1):]
|
||||
# logger.debug("... : '%s'", s)
|
||||
return -1
|
||||
|
||||
@staticmethod
|
||||
def _parse(s):
|
||||
types = {}
|
||||
values = {}
|
||||
scan = True
|
||||
# logger.debug("===: '%s'", s)
|
||||
# parse id
|
||||
p = s.find(',')
|
||||
if p == -1:
|
||||
p = s.index(';')
|
||||
scan = False
|
||||
name = s[:p]
|
||||
# parse fields
|
||||
while scan:
|
||||
s = s[(p + 2):] # skip 'name, ' / 'value, '
|
||||
# logger.debug("...: '%s'", s)
|
||||
p = s.index('=')
|
||||
k = s[:p]
|
||||
if not s[p + 1] == '(':
|
||||
raise ValueError
|
||||
s = s[(p + 2):] # skip 'key=('
|
||||
p = s.index(')')
|
||||
t = s[:p]
|
||||
s = s[(p + 1):] # skip 'type)'
|
||||
|
||||
if s[0] == '"':
|
||||
s = s[1:] # skip '"'
|
||||
p = Structure._find_eos(s)
|
||||
if p == -1:
|
||||
raise ValueError
|
||||
v = s[:(p - 1)]
|
||||
if s[p] == ';':
|
||||
scan = False
|
||||
# unescape \., but not \\. (using a backref)
|
||||
# need a reverse for re.escape()
|
||||
v = v.replace('\\\\', '\\')
|
||||
v = UNESCAPE.sub(r'\1', v)
|
||||
else:
|
||||
p = s.find(',')
|
||||
if p == -1:
|
||||
p = s.index(';')
|
||||
scan = False
|
||||
v = s[:p]
|
||||
|
||||
if t == 'structure':
|
||||
v = Structure(v)
|
||||
elif t == 'string' and v[0] == '"':
|
||||
v = v[1:-1]
|
||||
elif t == 'boolean':
|
||||
v = (v == '1')
|
||||
elif t in INT_TYPES:
|
||||
v = int(v)
|
||||
types[k] = t
|
||||
values[k] = v
|
||||
return (name, types, values)
|
79
tracer/tracer/structure_perf.py
Normal file
79
tracer/tracer/structure_perf.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
import timeit
|
||||
|
||||
from structure import Structure
|
||||
from gi.repository import Gst
|
||||
Gst.init(None)
|
||||
|
||||
PLAIN_STRUCTURE = r'thread-rusage, thread-id=(guint64)37268592, ts=(guint64)79416000, average-cpuload=(uint)1000, current-cpuload=(uint)1000, time=(guint64)79418045;'
|
||||
NESTED_STRUCTURE = r'latency.class, src=(structure)"scope\,\ type\=\(type\)gchararray\,\ related-to\=\(GstTracerValueScope\)GST_TRACER_VALUE_SCOPE_PAD\;", sink=(structure)"scope\,\ type\=\(type\)gchararray\,\ related-to\=\(GstTracerValueScope\)GST_TRACER_VALUE_SCOPE_PAD\;", time=(structure)"value\,\ type\=\(type\)guint64\,\ description\=\(string\)\"time\\\ it\\\ took\\\ for\\\ the\\\ buffer\\\ to\\\ go\\\ from\\\ src\\\ to\\\ sink\\\ ns\"\,\ flags\=\(GstTracerValueFlags\)GST_TRACER_VALUE_FLAGS_AGGREGATED\,\ min\=\(guint64\)0\,\ max\=\(guint64\)18446744073709551615\;";'
|
||||
|
||||
NAT_STRUCTURE = Structure(PLAIN_STRUCTURE)
|
||||
GI_STRUCTURE = Gst.Structure.from_string(PLAIN_STRUCTURE)[0]
|
||||
|
||||
|
||||
# native python impl
|
||||
|
||||
def nat_parse_plain():
|
||||
s = Structure(PLAIN_STRUCTURE)
|
||||
|
||||
|
||||
def nat_parse_nested():
|
||||
s = Structure(NESTED_STRUCTURE)
|
||||
|
||||
|
||||
def nat_get_name():
|
||||
return NAT_STRUCTURE.name
|
||||
|
||||
|
||||
def nat_get_value():
|
||||
return NAT_STRUCTURE.values['thread-id']
|
||||
|
||||
|
||||
# gstreamer impl via gi
|
||||
|
||||
def gi_parse_plain():
|
||||
s = Gst.Structure.from_string(PLAIN_STRUCTURE)[0]
|
||||
|
||||
|
||||
def gi_parse_nested():
|
||||
s = Gst.Structure.from_string(NESTED_STRUCTURE)[0]
|
||||
|
||||
|
||||
def gi_get_name():
|
||||
return GI_STRUCTURE.get_name()
|
||||
|
||||
|
||||
def gi_get_value():
|
||||
return GI_STRUCTURE.get_value('thread-id')
|
||||
|
||||
|
||||
# perf test
|
||||
|
||||
def perf(method, n, flavor):
|
||||
t = timeit.timeit(method + '()', 'from __main__ import ' + method, number=n)
|
||||
print("%6s: %lf s, (%lf calls/s)" % (flavor, t, (n / t)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-i', '--iterations', default=10000, type=int,
|
||||
help='number of iterations (default: 10000)')
|
||||
args = parser.parse_args()
|
||||
n = args.iterations
|
||||
|
||||
print("parse_plain:")
|
||||
t = perf('nat_parse_plain', n, 'native')
|
||||
t = perf('gi_parse_plain', n, 'gi')
|
||||
|
||||
print("parse_nested:")
|
||||
t = perf('nat_parse_nested', n, 'native')
|
||||
t = perf('gi_parse_nested', n, 'gi')
|
||||
|
||||
print("get_name:")
|
||||
t = perf('nat_get_name', n, 'native')
|
||||
t = perf('gi_get_name', n, 'gi')
|
||||
|
||||
print("get_value:")
|
||||
t = perf('nat_get_value', n, 'native')
|
||||
t = perf('gi_get_value', n, 'gi')
|
96
tracer/tracer/structure_test.py
Normal file
96
tracer/tracer/structure_test.py
Normal file
|
@ -0,0 +1,96 @@
|
|||
import logging
|
||||
import unittest
|
||||
|
||||
from tracer.structure import Structure
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
BAD_NAME = r'foo bar'
|
||||
BAD_KEY = r'foo, bar'
|
||||
BAD_TYPE1 = r'foo, bar=['
|
||||
BAD_TYPE2 = r'foo, bar=(int'
|
||||
|
||||
EMPTY_STRUCTURE = r'foo;'
|
||||
|
||||
SINGLE_VALUE_STRUCTURE = r'foo, key=(string)"value";'
|
||||
MISC_TYPES_STRUCTURE = r'foo, key1=(string)"value", key2=(int)5, key3=(boolean)1;'
|
||||
|
||||
NESTED_STRUCTURE = r'foo, nested=(structure)"bar\,\ key1\=\(int\)0\,\ key2\=\(int\)5\;";'
|
||||
|
||||
REGRESSIONS = [
|
||||
r'query, thread-id=(guint64)139839438879824, ts=(guint64)220860464, pad-ix=(uint)8, element-ix=(uint)9, peer-pad-ix=(uint)9, peer-element-ix=(uint)8, name=(string)accept-caps, structure=(structure)"GstQueryAcceptCaps\,\ caps\=\(GstCaps\)\"audio/mpeg\\\,\\\ mpegversion\\\=\\\(int\\\)4\\\,\\\ framed\\\=\\\(boolean\\\)true\\\,\\\ stream-format\\\=\\\(string\\\)raw\\\,\\\ level\\\=\\\(string\\\)2\\\,\\\ base-profile\\\=\\\(string\\\)lc\\\,\\\ profile\\\=\\\(string\\\)lc\\\,\\\ codec_data\\\=\\\(buffer\\\)1210\\\,\\\ rate\\\=\\\(int\\\)44100\\\,\\\ channels\\\=\\\(int\\\)2\"\,\ result\=\(boolean\)false\;", have-res=(boolean)0, res=(boolean)0;',
|
||||
r'message, thread-id=(guint64)139838900680560, ts=(guint64)1000451258, element-ix=(uint)2, name=(string)tag, structure=(structure)"GstMessageTag\,\ taglist\=\(taglist\)\"taglist\\\,\\\ datetime\\\=\\\(datetime\\\)2009-03-05T12:57:08Z\\\,\\\ private-qt-tag\\\=\\\(sample\\\)\\\{\\\ 00000019677373740000001164617461000000010000000030:None:R3N0U2VnbWVudCwgZmxhZ3M9KEdzdFNlZ21lbnRGbGFncylHU1RfU0VHTUVOVF9GTEFHX05PTkUsIHJhdGU9KGRvdWJsZSkxLCBhcHBsaWVkLXJhdGU9KGRvdWJsZSkxLCBmb3JtYXQ9KEdzdEZvcm1hdClHU1RfRk9STUFUX1RJTUUsIGJhc2U9KGd1aW50NjQpMCwgb2Zmc2V0PShndWludDY0KTAsIHN0YXJ0PShndWludDY0KTAsIHN0b3A9KGd1aW50NjQpMTg0NDY3NDQwNzM3MDk1NTE2MTUsIHRpbWU9KGd1aW50NjQpMCwgcG9zaXRpb249KGd1aW50NjQpMCwgZHVyYXRpb249KGd1aW50NjQpMTg0NDY3NDQwNzM3MDk1NTE2MTU7AA__:YXBwbGljYXRpb24veC1nc3QtcXQtZ3NzdC10YWcsIHN0eWxlPShzdHJpbmcpaXR1bmVzOwA_\\\,\\\ 0000001e6773746400000016646174610000000100000000313335353130:None:R3N0U2VnbWVudCwgZmxhZ3M9KEdzdFNlZ21lbnRGbGFncylHU1RfU0VHTUVOVF9GTEFHX05PTkUsIHJhdGU9KGRvdWJsZSkxLCBhcHBsaWVkLXJhdGU9KGRvdWJsZSkxLCBmb3JtYXQ9KEdzdEZvcm1hdClHU1RfRk9STUFUX1RJTUUsIGJhc2U9KGd1aW50NjQpMCwgb2Zmc2V0PShndWludDY0KTAsIHN0YXJ0PShndWludDY0KTAsIHN0b3A9KGd1aW50NjQpMTg0NDY3NDQwNzM3MDk1NTE2MTUsIHRpbWU9KGd1aW50NjQpMCwgcG9zaXRpb249KGd1aW50NjQpMCwgZHVyYXRpb249KGd1aW50NjQpMTg0NDY3NDQwNzM3MDk1NTE2MTU7AA__:YXBwbGljYXRpb24veC1nc3QtcXQtZ3N0ZC10YWcsIHN0eWxlPShzdHJpbmcpaXR1bmVzOwA_\\\,\\\ 0000003867737364000000306461746100000001000000004244354241453530354d4d313239353033343539373733353435370000000000:None:R3N0U2VnbWVudCwgZmxhZ3M9KEdzdFNlZ21lbnRGbGFncylHU1RfU0VHTUVOVF9GTEFHX05PTkUsIHJhdGU9KGRvdWJsZSkxLCBhcHBsaWVkLXJhdGU9KGRvdWJsZSkxLCBmb3JtYXQ9KEdzdEZvcm1hdClHU1RfRk9STUFUX1RJTUUsIGJhc2U9KGd1aW50NjQpMCwgb2Zmc2V0PShndWludDY0KTAsIHN0YXJ0PShndWludDY0KTAsIHN0b3A9KGd1aW50NjQpMTg0NDY3NDQwNzM3MDk1NTE2MTUsIHRpbWU9KGd1aW50NjQpMCwgcG9zaXRpb249KGd1aW50NjQpMCwgZHVyYXRpb249KGd1aW50NjQpMTg0NDY3NDQwNzM3MDk1NTE2MTU7AA__:YXBwbGljYXRpb24veC1nc3QtcXQtZ3NzZC10YWcsIHN0eWxlPShzdHJpbmcpaXR1bmVzOwA_\\\,\\\ 0000009867737075000000906461746100000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000:None:R3N0U2VnbWVudCwgZmxhZ3M9KEdzdFNlZ21lbnRGbGFncylHU1RfU0VHTUVOVF9GTEFHX05PTkUsIHJhdGU9KGRvdWJsZSkxLCBhcHBsaWVkLXJhdGU9KGRvdWJsZSkxLCBmb3JtYXQ9KEdzdEZvcm1hdClHU1RfRk9STUFUX1RJTUUsIGJhc2U9KGd1aW50NjQpMCwgb2Zmc2V0PShndWludDY0KTAsIHN0YXJ0PShndWludDY0KTAsIHN0b3A9KGd1aW50NjQpMTg0NDY3NDQwNzM3MDk1NTE2MTUsIHRpbWU9KGd1aW50NjQpMCwgcG9zaXRpb249KGd1aW50NjQpMCwgZHVyYXRpb249KGd1aW50NjQpMTg0NDY3NDQwNzM3MDk1NTE2MTU7AA__:YXBwbGljYXRpb24veC1nc3QtcXQtZ3NwdS10YWcsIHN0eWxlPShzdHJpbmcpaXR1bmVzOwA_\\\,\\\ 000000986773706d000000906461746100000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000:None:R3N0U2VnbWVudCwgZmxhZ3M9KEdzdFNlZ21lbnRGbGFncylHU1RfU0VHTUVOVF9GTEFHX05PTkUsIHJhdGU9KGRvdWJsZSkxLCBhcHBsaWVkLXJhdGU9KGRvdWJsZSkxLCBmb3JtYXQ9KEdzdEZvcm1hdClHU1RfRk9STUFUX1RJTUUsIGJhc2U9KGd1aW50NjQpMCwgb2Zmc2V0PShndWludDY0KTAsIHN0YXJ0PShndWludDY0KTAsIHN0b3A9KGd1aW50NjQpMTg0NDY3NDQwNzM3MDk1NTE2MTUsIHRpbWU9KGd1aW50NjQpMCwgcG9zaXRpb249KGd1aW50NjQpMCwgZHVyYXRpb249KGd1aW50NjQpMTg0NDY3NDQwNzM3MDk1NTE2MTU7AA__:YXBwbGljYXRpb24veC1nc3QtcXQtZ3NwbS10YWcsIHN0eWxlPShzdHJpbmcpaXR1bmVzOwA_\\\,\\\ 0000011867736868000001106461746100000001000000007631302e6c736361636865332e632e796f75747562652e636f6d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000:None:R3N0U2VnbWVudCwgZmxhZ3M9KEdzdFNlZ21lbnRGbGFncylHU1RfU0VHTUVOVF9GTEFHX05PTkUsIHJhdGU9KGRvdWJsZSkxLCBhcHBsaWVkLXJhdGU9KGRvdWJsZSkxLCBmb3JtYXQ9KEdzdEZvcm1hdClHU1RfRk9STUFUX1RJTUUsIGJhc2U9KGd1aW50NjQpMCwgb2Zmc2V0PShndWludDY0KTAsIHN0YXJ0PShndWludDY0KTAsIHN0b3A9KGd1aW50NjQpMTg0NDY3NDQwNzM3MDk1NTE2MTUsIHRpbWU9KGd1aW50NjQpMCwgcG9zaXRpb249KGd1aW50NjQpMCwgZHVyYXRpb249KGd1aW50NjQpMTg0NDY3NDQwNzM3MDk1NTE2MTU7AA__:YXBwbGljYXRpb24veC1nc3QtcXQtZ3NoaC10YWcsIHN0eWxlPShzdHJpbmcpaXR1bmVzOwA_\\\ \\\}\\\,\\\ container-format\\\=\\\(string\\\)\\\"ISO\\\\\\\ MP4/M4A\\\"\\\;\"\;";',
|
||||
]
|
||||
|
||||
|
||||
class TestStructure(unittest.TestCase):
|
||||
|
||||
def test_handles_bad_name(self):
|
||||
structure = None
|
||||
with self.assertRaises(ValueError):
|
||||
structure = Structure(BAD_NAME)
|
||||
|
||||
def test_handles_bad_key(self):
|
||||
structure = None
|
||||
with self.assertRaises(ValueError):
|
||||
structure = Structure(BAD_KEY)
|
||||
|
||||
def test_handles_bad_type1(self):
|
||||
structure = None
|
||||
with self.assertRaises(ValueError):
|
||||
structure = Structure(BAD_TYPE1)
|
||||
|
||||
def test_handles_bad_type2(self):
|
||||
structure = None
|
||||
with self.assertRaises(ValueError):
|
||||
structure = Structure(BAD_TYPE2)
|
||||
|
||||
def test_parses_empty_structure(self):
|
||||
structure = Structure(EMPTY_STRUCTURE)
|
||||
self.assertEqual(structure.text, EMPTY_STRUCTURE)
|
||||
|
||||
def test_parses_name_in_empty_structure(self):
|
||||
structure = Structure(EMPTY_STRUCTURE)
|
||||
self.assertEqual(structure.name, 'foo')
|
||||
|
||||
def test_parses_single_value_structure(self):
|
||||
structure = Structure(SINGLE_VALUE_STRUCTURE)
|
||||
self.assertEqual(structure.text, SINGLE_VALUE_STRUCTURE)
|
||||
|
||||
def test_parses_name(self):
|
||||
structure = Structure(SINGLE_VALUE_STRUCTURE)
|
||||
self.assertEqual(structure.name, 'foo')
|
||||
|
||||
def test_parses_key(self):
|
||||
structure = Structure(SINGLE_VALUE_STRUCTURE)
|
||||
self.assertIn('key', structure.types)
|
||||
self.assertIn('key', structure.values)
|
||||
|
||||
def test_parses_type(self):
|
||||
structure = Structure(SINGLE_VALUE_STRUCTURE)
|
||||
self.assertEqual(structure.types['key'], 'string')
|
||||
|
||||
def test_parses_string_value(self):
|
||||
structure = Structure(MISC_TYPES_STRUCTURE)
|
||||
self.assertEqual(structure.values['key1'], 'value')
|
||||
|
||||
def test_parses_int_value(self):
|
||||
structure = Structure(MISC_TYPES_STRUCTURE)
|
||||
self.assertEqual(structure.values['key2'], 5)
|
||||
|
||||
def test_parses_boolean_value(self):
|
||||
structure = Structure(MISC_TYPES_STRUCTURE)
|
||||
self.assertEqual(structure.values['key3'], True)
|
||||
|
||||
def test_parses_nested_structure(self):
|
||||
structure = Structure(NESTED_STRUCTURE)
|
||||
self.assertEqual(structure.text, NESTED_STRUCTURE)
|
||||
|
||||
def test_nested_structure_has_sub_structure(self):
|
||||
structure = Structure(NESTED_STRUCTURE)
|
||||
self.assertEqual(structure.types['nested'], 'structure')
|
||||
self.assertIsInstance(structure.values['nested'], Structure)
|
||||
|
||||
def test_regressions(self):
|
||||
for s in REGRESSIONS:
|
||||
structure = Structure(s)
|
2
validate/.gitignore
vendored
Normal file
2
validate/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*~
|
||||
build*/
|
504
validate/COPYING
Normal file
504
validate/COPYING
Normal file
|
@ -0,0 +1,504 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 2.1, February 1999
|
||||
|
||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
[This is the first released version of the Lesser GPL. It also counts
|
||||
as the successor of the GNU Library Public License, version 2, hence
|
||||
the version number 2.1.]
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
Licenses are intended to guarantee your freedom to share and change
|
||||
free software--to make sure the software is free for all its users.
|
||||
|
||||
This license, the Lesser General Public License, applies to some
|
||||
specially designated software packages--typically libraries--of the
|
||||
Free Software Foundation and other authors who decide to use it. You
|
||||
can use it too, but we suggest you first think carefully about whether
|
||||
this license or the ordinary General Public License is the better
|
||||
strategy to use in any particular case, based on the explanations below.
|
||||
|
||||
When we speak of free software, we are referring to freedom of use,
|
||||
not price. Our General Public Licenses are designed to make sure that
|
||||
you have the freedom to distribute copies of free software (and charge
|
||||
for this service if you wish); that you receive source code or can get
|
||||
it if you want it; that you can change the software and use pieces of
|
||||
it in new free programs; and that you are informed that you can do
|
||||
these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
distributors to deny you these rights or to ask you to surrender these
|
||||
rights. These restrictions translate to certain responsibilities for
|
||||
you if you distribute copies of the library or if you modify it.
|
||||
|
||||
For example, if you distribute copies of the library, whether gratis
|
||||
or for a fee, you must give the recipients all the rights that we gave
|
||||
you. You must make sure that they, too, receive or can get the source
|
||||
code. If you link other code with the library, you must provide
|
||||
complete object files to the recipients, so that they can relink them
|
||||
with the library after making changes to the library and recompiling
|
||||
it. And you must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with a two-step method: (1) we copyright the
|
||||
library, and (2) we offer you this license, which gives you legal
|
||||
permission to copy, distribute and/or modify the library.
|
||||
|
||||
To protect each distributor, we want to make it very clear that
|
||||
there is no warranty for the free library. Also, if the library is
|
||||
modified by someone else and passed on, the recipients should know
|
||||
that what they have is not the original version, so that the original
|
||||
author's reputation will not be affected by problems that might be
|
||||
introduced by others.
|
||||
|
||||
Finally, software patents pose a constant threat to the existence of
|
||||
any free program. We wish to make sure that a company cannot
|
||||
effectively restrict the users of a free program by obtaining a
|
||||
restrictive license from a patent holder. Therefore, we insist that
|
||||
any patent license obtained for a version of the library must be
|
||||
consistent with the full freedom of use specified in this license.
|
||||
|
||||
Most GNU software, including some libraries, is covered by the
|
||||
ordinary GNU General Public License. This license, the GNU Lesser
|
||||
General Public License, applies to certain designated libraries, and
|
||||
is quite different from the ordinary General Public License. We use
|
||||
this license for certain libraries in order to permit linking those
|
||||
libraries into non-free programs.
|
||||
|
||||
When a program is linked with a library, whether statically or using
|
||||
a shared library, the combination of the two is legally speaking a
|
||||
combined work, a derivative of the original library. The ordinary
|
||||
General Public License therefore permits such linking only if the
|
||||
entire combination fits its criteria of freedom. The Lesser General
|
||||
Public License permits more lax criteria for linking other code with
|
||||
the library.
|
||||
|
||||
We call this license the "Lesser" General Public License because it
|
||||
does Less to protect the user's freedom than the ordinary General
|
||||
Public License. It also provides other free software developers Less
|
||||
of an advantage over competing non-free programs. These disadvantages
|
||||
are the reason we use the ordinary General Public License for many
|
||||
libraries. However, the Lesser license provides advantages in certain
|
||||
special circumstances.
|
||||
|
||||
For example, on rare occasions, there may be a special need to
|
||||
encourage the widest possible use of a certain library, so that it becomes
|
||||
a de-facto standard. To achieve this, non-free programs must be
|
||||
allowed to use the library. A more frequent case is that a free
|
||||
library does the same job as widely used non-free libraries. In this
|
||||
case, there is little to gain by limiting the free library to free
|
||||
software only, so we use the Lesser General Public License.
|
||||
|
||||
In other cases, permission to use a particular library in non-free
|
||||
programs enables a greater number of people to use a large body of
|
||||
free software. For example, permission to use the GNU C Library in
|
||||
non-free programs enables many more people to use the whole GNU
|
||||
operating system, as well as its variant, the GNU/Linux operating
|
||||
system.
|
||||
|
||||
Although the Lesser General Public License is Less protective of the
|
||||
users' freedom, it does ensure that the user of a program that is
|
||||
linked with the Library has the freedom and the wherewithal to run
|
||||
that program using a modified version of the Library.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow. Pay close attention to the difference between a
|
||||
"work based on the library" and a "work that uses the library". The
|
||||
former contains code derived from the library, whereas the latter must
|
||||
be combined with the library in order to run.
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License Agreement applies to any software library or other
|
||||
program which contains a notice placed by the copyright holder or
|
||||
other authorized party saying it may be distributed under the terms of
|
||||
this Lesser General Public License (also called "this License").
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
A "library" means a collection of software functions and/or data
|
||||
prepared so as to be conveniently linked with application programs
|
||||
(which use some of those functions and data) to form executables.
|
||||
|
||||
The "Library", below, refers to any such software library or work
|
||||
which has been distributed under these terms. A "work based on the
|
||||
Library" means either the Library or any derivative work under
|
||||
copyright law: that is to say, a work containing the Library or a
|
||||
portion of it, either verbatim or with modifications and/or translated
|
||||
straightforwardly into another language. (Hereinafter, translation is
|
||||
included without limitation in the term "modification".)
|
||||
|
||||
"Source code" for a work means the preferred form of the work for
|
||||
making modifications to it. For a library, complete source code means
|
||||
all the source code for all modules it contains, plus any associated
|
||||
interface definition files, plus the scripts used to control compilation
|
||||
and installation of the library.
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running a program using the Library is not restricted, and output from
|
||||
such a program is covered only if its contents constitute a work based
|
||||
on the Library (independent of the use of the Library in a tool for
|
||||
writing it). Whether that is true depends on what the Library does
|
||||
and what the program that uses the Library does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Library's
|
||||
complete source code as you receive it, in any medium, provided that
|
||||
you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
||||
all the notices that refer to this License and to the absence of any
|
||||
warranty; and distribute a copy of this License along with the
|
||||
Library.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy,
|
||||
and you may at your option offer warranty protection in exchange for a
|
||||
fee.
|
||||
|
||||
2. You may modify your copy or copies of the Library or any portion
|
||||
of it, thus forming a work based on the Library, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) The modified work must itself be a software library.
|
||||
|
||||
b) You must cause the files modified to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
c) You must cause the whole of the work to be licensed at no
|
||||
charge to all third parties under the terms of this License.
|
||||
|
||||
d) If a facility in the modified Library refers to a function or a
|
||||
table of data to be supplied by an application program that uses
|
||||
the facility, other than as an argument passed when the facility
|
||||
is invoked, then you must make a good faith effort to ensure that,
|
||||
in the event an application does not supply such function or
|
||||
table, the facility still operates, and performs whatever part of
|
||||
its purpose remains meaningful.
|
||||
|
||||
(For example, a function in a library to compute square roots has
|
||||
a purpose that is entirely well-defined independent of the
|
||||
application. Therefore, Subsection 2d requires that any
|
||||
application-supplied function or table used by this function must
|
||||
be optional: if the application does not supply it, the square
|
||||
root function must still compute square roots.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Library,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Library, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote
|
||||
it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Library.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Library
|
||||
with the Library (or with a work based on the Library) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
||||
License instead of this License to a given copy of the Library. To do
|
||||
this, you must alter all the notices that refer to this License, so
|
||||
that they refer to the ordinary GNU General Public License, version 2,
|
||||
instead of to this License. (If a newer version than version 2 of the
|
||||
ordinary GNU General Public License has appeared, then you can specify
|
||||
that version instead if you wish.) Do not make any other change in
|
||||
these notices.
|
||||
|
||||
Once this change is made in a given copy, it is irreversible for
|
||||
that copy, so the ordinary GNU General Public License applies to all
|
||||
subsequent copies and derivative works made from that copy.
|
||||
|
||||
This option is useful when you wish to copy part of the code of
|
||||
the Library into a program that is not a library.
|
||||
|
||||
4. You may copy and distribute the Library (or a portion or
|
||||
derivative of it, under Section 2) in object code or executable form
|
||||
under the terms of Sections 1 and 2 above provided that you accompany
|
||||
it with the complete corresponding machine-readable source code, which
|
||||
must be distributed under the terms of Sections 1 and 2 above on a
|
||||
medium customarily used for software interchange.
|
||||
|
||||
If distribution of object code is made by offering access to copy
|
||||
from a designated place, then offering equivalent access to copy the
|
||||
source code from the same place satisfies the requirement to
|
||||
distribute the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
5. A program that contains no derivative of any portion of the
|
||||
Library, but is designed to work with the Library by being compiled or
|
||||
linked with it, is called a "work that uses the Library". Such a
|
||||
work, in isolation, is not a derivative work of the Library, and
|
||||
therefore falls outside the scope of this License.
|
||||
|
||||
However, linking a "work that uses the Library" with the Library
|
||||
creates an executable that is a derivative of the Library (because it
|
||||
contains portions of the Library), rather than a "work that uses the
|
||||
library". The executable is therefore covered by this License.
|
||||
Section 6 states terms for distribution of such executables.
|
||||
|
||||
When a "work that uses the Library" uses material from a header file
|
||||
that is part of the Library, the object code for the work may be a
|
||||
derivative work of the Library even though the source code is not.
|
||||
Whether this is true is especially significant if the work can be
|
||||
linked without the Library, or if the work is itself a library. The
|
||||
threshold for this to be true is not precisely defined by law.
|
||||
|
||||
If such an object file uses only numerical parameters, data
|
||||
structure layouts and accessors, and small macros and small inline
|
||||
functions (ten lines or less in length), then the use of the object
|
||||
file is unrestricted, regardless of whether it is legally a derivative
|
||||
work. (Executables containing this object code plus portions of the
|
||||
Library will still fall under Section 6.)
|
||||
|
||||
Otherwise, if the work is a derivative of the Library, you may
|
||||
distribute the object code for the work under the terms of Section 6.
|
||||
Any executables containing that work also fall under Section 6,
|
||||
whether or not they are linked directly with the Library itself.
|
||||
|
||||
6. As an exception to the Sections above, you may also combine or
|
||||
link a "work that uses the Library" with the Library to produce a
|
||||
work containing portions of the Library, and distribute that work
|
||||
under terms of your choice, provided that the terms permit
|
||||
modification of the work for the customer's own use and reverse
|
||||
engineering for debugging such modifications.
|
||||
|
||||
You must give prominent notice with each copy of the work that the
|
||||
Library is used in it and that the Library and its use are covered by
|
||||
this License. You must supply a copy of this License. If the work
|
||||
during execution displays copyright notices, you must include the
|
||||
copyright notice for the Library among them, as well as a reference
|
||||
directing the user to the copy of this License. Also, you must do one
|
||||
of these things:
|
||||
|
||||
a) Accompany the work with the complete corresponding
|
||||
machine-readable source code for the Library including whatever
|
||||
changes were used in the work (which must be distributed under
|
||||
Sections 1 and 2 above); and, if the work is an executable linked
|
||||
with the Library, with the complete machine-readable "work that
|
||||
uses the Library", as object code and/or source code, so that the
|
||||
user can modify the Library and then relink to produce a modified
|
||||
executable containing the modified Library. (It is understood
|
||||
that the user who changes the contents of definitions files in the
|
||||
Library will not necessarily be able to recompile the application
|
||||
to use the modified definitions.)
|
||||
|
||||
b) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (1) uses at run time a
|
||||
copy of the library already present on the user's computer system,
|
||||
rather than copying library functions into the executable, and (2)
|
||||
will operate properly with a modified version of the library, if
|
||||
the user installs one, as long as the modified version is
|
||||
interface-compatible with the version that the work was made with.
|
||||
|
||||
c) Accompany the work with a written offer, valid for at
|
||||
least three years, to give the same user the materials
|
||||
specified in Subsection 6a, above, for a charge no more
|
||||
than the cost of performing this distribution.
|
||||
|
||||
d) If distribution of the work is made by offering access to copy
|
||||
from a designated place, offer equivalent access to copy the above
|
||||
specified materials from the same place.
|
||||
|
||||
e) Verify that the user has already received a copy of these
|
||||
materials or that you have already sent this user a copy.
|
||||
|
||||
For an executable, the required form of the "work that uses the
|
||||
Library" must include any data and utility programs needed for
|
||||
reproducing the executable from it. However, as a special exception,
|
||||
the materials to be distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on
|
||||
which the executable runs, unless that component itself accompanies
|
||||
the executable.
|
||||
|
||||
It may happen that this requirement contradicts the license
|
||||
restrictions of other proprietary libraries that do not normally
|
||||
accompany the operating system. Such a contradiction means you cannot
|
||||
use both them and the Library together in an executable that you
|
||||
distribute.
|
||||
|
||||
7. You may place library facilities that are a work based on the
|
||||
Library side-by-side in a single library together with other library
|
||||
facilities not covered by this License, and distribute such a combined
|
||||
library, provided that the separate distribution of the work based on
|
||||
the Library and of the other library facilities is otherwise
|
||||
permitted, and provided that you do these two things:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities. This must be distributed under the terms of the
|
||||
Sections above.
|
||||
|
||||
b) Give prominent notice with the combined library of the fact
|
||||
that part of it is a work based on the Library, and explaining
|
||||
where to find the accompanying uncombined form of the same work.
|
||||
|
||||
8. You may not copy, modify, sublicense, link with, or distribute
|
||||
the Library except as expressly provided under this License. Any
|
||||
attempt otherwise to copy, modify, sublicense, link with, or
|
||||
distribute the Library is void, and will automatically terminate your
|
||||
rights under this License. However, parties who have received copies,
|
||||
or rights, from you under this License will not have their licenses
|
||||
terminated so long as such parties remain in full compliance.
|
||||
|
||||
9. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Library or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Library (or any work based on the
|
||||
Library), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Library or works based on it.
|
||||
|
||||
10. Each time you redistribute the Library (or any work based on the
|
||||
Library), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute, link with or modify the Library
|
||||
subject to these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties with
|
||||
this License.
|
||||
|
||||
11. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Library at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Library by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Library.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply,
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
12. If the distribution and/or use of the Library is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Library under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
13. The Free Software Foundation may publish revised and/or new
|
||||
versions of the Lesser General Public License from time to time.
|
||||
Such new versions will be similar in spirit to the present version,
|
||||
but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
specifies a version number of this License which applies to it and
|
||||
"any later version", you have the option of following the terms and
|
||||
conditions either of that version or of any later version published by
|
||||
the Free Software Foundation. If the Library does not specify a
|
||||
license version number, you may choose any version ever published by
|
||||
the Free Software Foundation.
|
||||
|
||||
14. If you wish to incorporate parts of the Library into other free
|
||||
programs whose distribution conditions are incompatible with these,
|
||||
write to the author to ask for permission. For software which is
|
||||
copyrighted by the Free Software Foundation, write to the Free
|
||||
Software Foundation; we sometimes make exceptions for this. Our
|
||||
decision will be guided by the two goals of preserving the free status
|
||||
of all derivatives of our free software and of promoting the sharing
|
||||
and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Libraries
|
||||
|
||||
If you develop a new library, and you want it to be of the greatest
|
||||
possible use to the public, we recommend making it free software that
|
||||
everyone can redistribute and change. You can do so by permitting
|
||||
redistribution under these terms (or, alternatively, under the terms of the
|
||||
ordinary General Public License).
|
||||
|
||||
To apply these terms, attach the following notices to the library. It is
|
||||
safest to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least the
|
||||
"copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the library's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1990
|
||||
Ty Coon, President of Vice
|
||||
|
||||
That's all there is to it!
|
||||
|
||||
|
42
validate/README
Normal file
42
validate/README
Normal file
|
@ -0,0 +1,42 @@
|
|||
== Gst-Validate
|
||||
|
||||
The goal of GstValidate is to be able to detect when elements are not
|
||||
behaving as expected and report it to the user so he knows how things
|
||||
are supposed to work inside a GstPipeline. In the end, fixing issues
|
||||
found by the tool will ensure that all elements behave all together in
|
||||
the expected way.
|
||||
|
||||
The easiest way of using GstValidate is to use one of its command-line
|
||||
tools, located at tools/ directory. It is also possible to monitor
|
||||
GstPipelines from any application by using the LD_PRELOAD gstvalidate
|
||||
lib. The third way of using it is to write your own application that
|
||||
links and uses libgstvalidate.
|
||||
|
||||
== BUILDING
|
||||
|
||||
Getting the code:
|
||||
|
||||
Releases are available at <URL>, download and extract the tarball. If you
|
||||
want to use latest git version, do:
|
||||
|
||||
git clone <URI>
|
||||
|
||||
After cloning or extracting from a tarball, enter the gst-validate directory:
|
||||
|
||||
cd gst-validate
|
||||
|
||||
Build with:
|
||||
|
||||
meson build --prefix=<installation-prefix>
|
||||
ninja -C build
|
||||
sudo ninja -C build install (only if you want to install it)
|
||||
|
||||
Replace <installation-prefix> with your desired installation path, you can omit
|
||||
the --prefix argument if you aren't going to install it or if you want the
|
||||
default /usr/local. It is possible to use gst-validate CLI tools without
|
||||
installation.
|
||||
|
||||
== INSTRUCTIONS
|
||||
|
||||
If you are looking for informations on how to use gst-validate -> docs/validate-usage.txt
|
||||
If you are looking for informations on gst-validate design -> docs/validate-design.txt
|
142
validate/data/bash-completion/completions/gst-validate-1.0
Normal file
142
validate/data/bash-completion/completions/gst-validate-1.0
Normal file
|
@ -0,0 +1,142 @@
|
|||
# GStreamer
|
||||
# Copyright (C) 2015 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com>
|
||||
# Copyright (C) 2021 Stéphane Cerveau <scerveau@collabora.com>
|
||||
#
|
||||
# bash/zsh completion support for gst-validate
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
_GST_HELPERDIR="${BASH_SOURCE[0]%/*}/../helpers"
|
||||
|
||||
if [[ ! -f $_GST_HELPERDIR/gst ]]; then
|
||||
_GST_HELPERDIR="$(pkg-config --variable=bashhelpersdir gstreamer-1.0)"
|
||||
else
|
||||
_GST_HELPERDIR=`cd "$_GST_HELPERDIR"; pwd`
|
||||
fi
|
||||
|
||||
# Common definitions
|
||||
. "$_GST_HELPERDIR"/gst
|
||||
|
||||
_gst_validate_all_arguments ()
|
||||
{
|
||||
_gst_all_arguments gst-validate-1.0
|
||||
}
|
||||
|
||||
_gst_complete_compatible_elements ()
|
||||
{
|
||||
COMPREPLY=( $(compgen -W "$($_GST_HELPER --compatible-with $previous_element)" -- $cur) )
|
||||
}
|
||||
|
||||
_gst_complete_all_elements ()
|
||||
{
|
||||
COMPREPLY=( $(compgen -W "$($_GST_HELPER -l)" -- $cur) )
|
||||
}
|
||||
|
||||
_gst_complete_element_properties ()
|
||||
{
|
||||
COMPREPLY=( $(compgen -W "$($_GST_HELPER --element-properties $previous_element)" -- $cur) )
|
||||
}
|
||||
|
||||
_gstvalidate___exclude_ () { _gst_mandatory_argument gst-validate-1.0; }
|
||||
|
||||
_gst_validate_main ()
|
||||
{
|
||||
local i=1 command function_exists previous_element have_previous_element=0 completion_func
|
||||
|
||||
while [[ $i -ne $COMP_CWORD ]];
|
||||
do
|
||||
local var
|
||||
var="${COMP_WORDS[i]}"
|
||||
if [[ "$var" == "-"* ]]
|
||||
then
|
||||
command="$var"
|
||||
fi
|
||||
i=$(($i+1))
|
||||
done
|
||||
|
||||
i=1
|
||||
while [[ $i -ne $COMP_CWORD ]];
|
||||
do
|
||||
local var
|
||||
var="${COMP_WORDS[i]}"
|
||||
|
||||
if [[ "$var" == "-"* ]]
|
||||
then
|
||||
i=$(($i+1))
|
||||
continue
|
||||
fi
|
||||
|
||||
$(gst-inspect-1.0 --exists $var)
|
||||
if [ $? -eq 0 ]
|
||||
then
|
||||
previous_element="$var"
|
||||
have_previous_element=1
|
||||
fi
|
||||
i=$(($i+1))
|
||||
done
|
||||
|
||||
if [[ "$command" == "--gst"* ]]; then
|
||||
completion_func="_${command//-/_}"
|
||||
else
|
||||
completion_func="_gstlaunch_${command//-/_}"
|
||||
fi
|
||||
|
||||
# Seems like bash doesn't like "exclude" in function names
|
||||
if [[ "$completion_func" == "_gstlaunch___exclude" ]]
|
||||
then
|
||||
completion_func="_gstvalidate___exclude_"
|
||||
fi
|
||||
|
||||
declare -f $completion_func >/dev/null 2>&1
|
||||
|
||||
function_exists=$?
|
||||
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
_gst_validate_all_arguments
|
||||
elif [ $function_exists -eq 0 ]
|
||||
then
|
||||
$completion_func
|
||||
elif [ $have_previous_element -ne 0 ] && [[ "$prev" == "!" ]]
|
||||
then
|
||||
_gst_complete_compatible_elements
|
||||
elif [ $have_previous_element -ne 0 ]
|
||||
then
|
||||
_gst_complete_element_properties
|
||||
else
|
||||
_gst_complete_all_elements
|
||||
fi
|
||||
}
|
||||
|
||||
_gst_validate_func_wrap ()
|
||||
{
|
||||
local cur prev
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
$1
|
||||
}
|
||||
|
||||
# Setup completion for certain functions defined above by setting common
|
||||
# variables and workarounds.
|
||||
# This is NOT a public function; use at your own risk.
|
||||
_gst_validate_complete ()
|
||||
{
|
||||
local wrapper="__launch_wrap${2}"
|
||||
eval "$wrapper () { _gst_validate_func_wrap $2 ; }"
|
||||
complete -o bashdefault -o default -o nospace -F $wrapper $1 2>/dev/null \
|
||||
|| complete -o default -o nospace -F $wrapper $1
|
||||
}
|
||||
|
||||
_gst_validate_complete gst-validate-1.0 _gst_validate_main
|
241
validate/data/gstvalidate.supp
Normal file
241
validate/data/gstvalidate.supp
Normal file
|
@ -0,0 +1,241 @@
|
|||
### This file contains either validate specific suppressions or bugs that we
|
||||
### can't easily address because they are lower in the stack.
|
||||
### All the other suppressions should be added ton gstreamer/tests/check/gstreamer.supp
|
||||
|
||||
### Each set of suppression rules should be prefixed by either:
|
||||
### - FIXED: if the bug/leak has been fixed upstream but we keep the rule
|
||||
### because the fix may not be deployed yet (because it's lower in the
|
||||
### stack and not in gst itself).
|
||||
### - PENDING: if the bug/leak hasn't been fixed yet. In this case, please
|
||||
### add an url to the bug report.
|
||||
|
||||
# PENDING: https://bugs.freedesktop.org/show_bug.cgi?id=90073
|
||||
{
|
||||
Leak in mesa fixed with http://lists.freedesktop.org/archives/mesa-dev/2015-April/082101.html
|
||||
Memcheck:Leak
|
||||
fun:malloc
|
||||
fun:read_packet
|
||||
fun:_xcb_in_read
|
||||
fun:_xcb_conn_wait
|
||||
fun:wait_for_reply
|
||||
fun:xcb_wait_for_reply
|
||||
fun:dri3_open
|
||||
fun:dri3_create_screen
|
||||
fun:AllocAndFetchScreenConfigs
|
||||
fun:__glXInitialize
|
||||
fun:glXQueryVersion
|
||||
}
|
||||
|
||||
{
|
||||
Leak in mesa fixed with http://lists.freedesktop.org/archives/mesa-dev/2015-April/082100.html
|
||||
Memcheck:Leak
|
||||
...
|
||||
fun:get_render_node_from_id_path_tag
|
||||
fun:loader_get_user_preferred_fd
|
||||
fun:dri3_create_screen
|
||||
fun:AllocAndFetchScreenConfigs
|
||||
fun:__glXInitialize
|
||||
fun:glXQueryVersion
|
||||
}
|
||||
|
||||
# FIXED
|
||||
{
|
||||
Fixed in pixman master
|
||||
Memcheck:Leak
|
||||
fun:memalign
|
||||
fun:allocate_and_init
|
||||
fun:tls_get_addr_tail
|
||||
}
|
||||
|
||||
# PENDING: https://bugzilla.gnome.org/show_bug.cgi?id=748417
|
||||
{
|
||||
Ignore all the invalid memory errors from libvpx
|
||||
Memcheck:Cond
|
||||
obj:/usr/lib64/libvpx.so*
|
||||
}
|
||||
|
||||
{
|
||||
Ignore all the invalid memory errors from libvpx
|
||||
Memcheck:Value8
|
||||
obj:/usr/lib64/libvpx.so*
|
||||
}
|
||||
|
||||
# PENDING: https://bugzilla.gnome.org/show_bug.cgi?id=747110
|
||||
{
|
||||
https://bugzilla.gnome.org/show_bug.cgi?id=747110
|
||||
Memcheck:Addr4
|
||||
...
|
||||
fun:aac_decode_frame_int
|
||||
fun:aac_decode_frame
|
||||
}
|
||||
|
||||
# PENDING: https://bugzilla.gnome.org/show_bug.cgi?id=752989
|
||||
{
|
||||
https://bugzilla.gnome.org/show_bug.cgi?id=752989
|
||||
Memcheck:Value4
|
||||
...
|
||||
fun:aac_decode_frame_int
|
||||
fun:aac_decode_frame
|
||||
}
|
||||
|
||||
# PENDING: https://bugs.freedesktop.org/show_bug.cgi?id=90194
|
||||
{
|
||||
https://bugs.freedesktop.org/show_bug.cgi?id=90194
|
||||
Memcheck:Param
|
||||
ioctl(generic)
|
||||
fun:ioctl
|
||||
fun:drmIoctl
|
||||
fun:drmPrimeHandleToFD
|
||||
}
|
||||
|
||||
# PENDING: https://bugzilla.gnome.org/show_bug.cgi?id=749232
|
||||
# x264 errors
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
...
|
||||
fun:x264_encoder_encode
|
||||
}
|
||||
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Value8
|
||||
...
|
||||
fun:x264_encoder_encode
|
||||
}
|
||||
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
...
|
||||
fun:x264_lookahead_thread
|
||||
}
|
||||
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Value8
|
||||
...
|
||||
fun:x264_lookahead_thread
|
||||
}
|
||||
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
...
|
||||
fun:x264_threadpool_thread
|
||||
}
|
||||
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Value8
|
||||
obj:/usr/lib64/libx264.so.*
|
||||
}
|
||||
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
obj:/usr/lib64/libx264.so.*
|
||||
}
|
||||
|
||||
# PENDING: https://bugzilla.gnome.org/show_bug.cgi?id=749428
|
||||
# Theora encoder
|
||||
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Value8
|
||||
...
|
||||
fun:theora_enc_handle_frame
|
||||
}
|
||||
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Cond
|
||||
...
|
||||
fun:theora_enc_handle_frame
|
||||
}
|
||||
|
||||
{
|
||||
<insert_a_suppression_name_here>
|
||||
Memcheck:Value8
|
||||
fun:oc_enc_tokenize_ac
|
||||
}
|
||||
|
||||
# FIXED
|
||||
{
|
||||
Fixed with mesa master
|
||||
Memcheck:Cond
|
||||
fun:lp_build_blend_factor_unswizzled
|
||||
...
|
||||
fun:gst_glimage_sink_on_draw
|
||||
}
|
||||
|
||||
# FIXED
|
||||
{
|
||||
Fixed with mesa master
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:calloc
|
||||
...
|
||||
fun:_do_convert_draw
|
||||
fun:_do_convert_one_view
|
||||
}
|
||||
|
||||
# FIXED
|
||||
{
|
||||
Fixed with mesa master
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:malloc
|
||||
...
|
||||
fun:gst_gl_shader_compile
|
||||
}
|
||||
|
||||
# FIXED
|
||||
{
|
||||
Fixed with mesa master
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite
|
||||
fun:calloc
|
||||
...
|
||||
fun:_draw_checker_background
|
||||
fun:_draw_background
|
||||
fun:gst_gl_video_mixer_callback
|
||||
}
|
||||
|
||||
{
|
||||
#https://bugs.freedesktop.org/show_bug.cgi?id=8215
|
||||
#https://bugs.freedesktop.org/show_bug.cgi?id=8428
|
||||
#FcPattern uses 'intptr_t elts_offset' instead of 'FcPatternEltPtr elts',
|
||||
#which confuses valgrind.
|
||||
font_config_bug_2
|
||||
Memcheck:Leak
|
||||
fun:*alloc
|
||||
...
|
||||
fun:Fc*Add*
|
||||
}
|
||||
{
|
||||
#Same root cause as font_config_bug_2.
|
||||
#The 'leak' here is a copy of rule values, as opposed to new values.
|
||||
font_config_bug_3
|
||||
Memcheck:Leak
|
||||
fun:*alloc
|
||||
...
|
||||
fun:FcConfigValues
|
||||
}
|
||||
{
|
||||
#Same root cause as font_config_bug_2.
|
||||
#The 'leak' is copies of font or pattern values into returned pattern values.
|
||||
font_config_bug_4
|
||||
Memcheck:Leak
|
||||
fun:*alloc
|
||||
...
|
||||
fun:FcValue*
|
||||
fun:FcFontRenderPrepare
|
||||
}
|
||||
{
|
||||
font_config_bug_6
|
||||
Memcheck:Leak
|
||||
fun:*alloc
|
||||
...
|
||||
obj:*/libfontconfig.so.*
|
||||
}
|
1
validate/data/meson.build
Normal file
1
validate/data/meson.build
Normal file
|
@ -0,0 +1 @@
|
|||
subdir('scenarios')
|
|
@ -0,0 +1,5 @@
|
|||
description, duration=15.0
|
||||
set-restriction, playback-time=5.0, restriction-caps="video/x-raw,framerate=(fraction)5/1"
|
||||
set-restriction, playback-time=10.0, restriction-caps="video/x-raw,framerate=(fraction)30/1"
|
||||
eos, playback-time=15.0
|
||||
stop, playback-time=15.0
|
|
@ -0,0 +1,7 @@
|
|||
description, duration=25.0
|
||||
set-restriction, playback-time=5.0, restriction-caps="video/x-raw,framerate=(fraction)5/1"
|
||||
set-restriction, playback-time=10.0, restriction-caps="video/x-raw,height=20,width=20,framerate=(fraction)5/1"
|
||||
set-restriction, playback-time=15.0, restriction-caps="video/x-raw,height=20,width=20,framerate=(fraction)30/1"
|
||||
set-restriction, playback-time=20.0, restriction-caps="video/x-raw,height=720,width=1280,framerate=(fraction)30/1"
|
||||
eos, playback-time=25.0
|
||||
stop, playback-time=25.0
|
5
validate/data/scenarios/adaptive_video_size.scenario
Normal file
5
validate/data/scenarios/adaptive_video_size.scenario
Normal file
|
@ -0,0 +1,5 @@
|
|||
description, duration=15.0
|
||||
set-restriction, playback-time=5.0, restriction-caps="video/x-raw,height=480,width=854"
|
||||
set-restriction, playback-time=10.0, restriction-caps="video/x-raw,height=720,width=1280"
|
||||
eos, playback-time=15.0
|
||||
stop, playback-time=15.0
|
|
@ -0,0 +1,14 @@
|
|||
description, duration=55.0, min-media-duration=470.0, seek=true, reverse-playback=true
|
||||
include,location=includes/default-seek-flags.scenario
|
||||
seek, name=backward-seek, playback-time=0.0, rate=-1.0, start=0.0, stop=310.0, flags="$(default_flags)"
|
||||
seek, name=forward-seek, playback-time=305.0, rate=1.0, start=305.0, flags="$(default_flags)"
|
||||
seek, name=Fast-forward-seek, playback-time=310.0, rate=2.0, start=310.0, flags="$(default_flags)"
|
||||
seek, name=Fast-backward-seek, playback-time=320.0, rate=-2.0, start=0.0, stop=320.0, flags="$(default_flags)"
|
||||
seek, name=Fast-forward-seek, playback-time=310.0, rate=4.0, start=310.0, flags="$(default_flags)"
|
||||
seek, name=Fast-backward-seek, playback-time=330.0, rate=-4.0, start=0.0, stop=330.0, flags="$(default_flags)"
|
||||
seek, name=Fast-forward-seek, playback-time=310.0, rate=8.0, start=310.0, flags="$(default_flags)"
|
||||
seek, name=Fast-backward-seek, playback-time=350.0, rate=-8.0, start=0.0, stop=350.0, flags="$(default_flags)"
|
||||
seek, name=Fast-forward-seek, playback-time=310.0, rate=16.0, start=310.0, flags="$(default_flags)"
|
||||
seek, name=Fast-backward-seek, playback-time=390.0, rate=-16.0, start=0.0, stop=390.0, flags="$(default_flags)"
|
||||
seek, name=Fast-forward-seek, playback-time=310.0, rate=32.0, start=310.0, flags="$(default_flags)"
|
||||
seek, name=Fast-backward-seek, playback-time=470.0, rate=-32.0, start=310.0, stop=470.0, flags="$(default_flags)"
|
4
validate/data/scenarios/camerabin_signal.scenario
Normal file
4
validate/data/scenarios/camerabin_signal.scenario
Normal file
|
@ -0,0 +1,4 @@
|
|||
description, duration=20.0
|
||||
emit-signal, target-element-name=camerabin0, signal-name=start-capture, playback-time=10.0
|
||||
eos, playback-time=20.0
|
||||
|
8
validate/data/scenarios/change_state_intensive.scenario
Normal file
8
validate/data/scenarios/change_state_intensive.scenario
Normal file
|
@ -0,0 +1,8 @@
|
|||
description, duration=0, summary="Set state to NULL->PLAYING->NULL 20 times", need-clock-sync=true, min-media-duration=1.0, live_content_compatible=True, handles-states=true, ignore-eos=true
|
||||
|
||||
foreach, i=[0, 40],
|
||||
actions = {
|
||||
"set-state, state=playing",
|
||||
"set-state, state=null",
|
||||
}
|
||||
stop;
|
|
@ -0,0 +1,6 @@
|
|||
description, summary="Disable subtitle track while pipeline is PAUSED", min-subtitle-track=2, duration=5.0, handles-states=true, needs_preroll=true
|
||||
pause;
|
||||
switch-track, name="Disable subtitle", type=text, disable=true
|
||||
wait, duration=0.5
|
||||
play;
|
||||
stop, playback-time=2.0
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue