├── po
├── meson.build
├── LINGUAS
├── POTFILES
├── fr.po
└── ru.po
├── .editorconfig
├── data
├── com.github.bilelmoussaoui.AudioCutter.gresource.xml
├── com.github.bilelmoussaoui.AudioCutter.desktop.in
├── style.css
├── icons
│ ├── meson.build
│ └── hicolor
│ │ ├── symbolic
│ │ └── apps
│ │ │ └── com.github.bilelmoussaoui.AudioCutter-symbolic.svg
│ │ └── scalable
│ │ └── apps
│ │ └── com.github.bilelmoussaoui.AudioCutter.svg
├── com.github.bilelmoussaoui.AudioCutter.appdata.xml.in
├── org.gnome.AudioCutter.gschema.xml
├── meson.build
└── com.github.bilelmoussaoui.AudioCutter.gschema.xml
├── TODO.md
├── AudioCutter
├── widgets
│ ├── loading.py
│ ├── shortcuts.py
│ ├── __init__.py
│ ├── about.py
│ ├── settings.py
│ ├── zoombox.py
│ ├── notification.py
│ ├── actionbar.py
│ ├── headerbar.py
│ ├── soundconfig.py
│ ├── time.py
│ ├── window.py
│ └── audio_graph.py
├── modules
│ ├── __init__.py
│ ├── exporter.py
│ ├── log.py
│ ├── settings.py
│ └── player.py
├── __init__.py
├── const.py
├── utils.py
├── objects.py
├── objects
│ └── renderer.c
└── application.py
├── circle.yml
├── README.md
├── .gitignore
├── tests
└── test_code_format.py
├── audio-cutter.py.in
├── meson.build
└── LICENSE
/po/meson.build:
--------------------------------------------------------------------------------
1 | i18n.gettext('audiocutter', preset: 'glib')
2 |
--------------------------------------------------------------------------------
/po/LINGUAS:
--------------------------------------------------------------------------------
1 | # please keep this list sorted alphabetically
2 | #
3 | fr
4 | ru
5 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | indent_size = 4
3 | indent_style = space
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
7 | [*.build]
8 | indent_size = 2
9 |
--------------------------------------------------------------------------------
/data/com.github.bilelmoussaoui.AudioCutter.gresource.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | style.css
5 |
6 |
7 |
--------------------------------------------------------------------------------
/data/com.github.bilelmoussaoui.AudioCutter.desktop.in:
--------------------------------------------------------------------------------
1 | [Desktop Entry]
2 | Name=Audio Cutter
3 | GenericName=Audio Cutter
4 | Comment=Cut your audio files easily
5 | Type=Application
6 | Exec=audio-cutter
7 | Terminal=false
8 | Categories=GNOME;GTK;Audio;
9 | Keywords=Gnome;GTK;Audio;Cutter;
10 | Icon=com.github.bilelmoussaoui.AudioCutter
11 | StartupNotify=true
12 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | # To-Do list
2 |
3 | - Make the waveform scrollable
4 | - Finish the export code
5 | - Flatpak file?
6 | - Create a setting window?
7 | - Add some nice shortcuts like CTRL+O to open a file?
8 | - Add those shortucts to the Shortcuts window
9 | - Create an icon?
10 | - Create any possible tests?
11 | - Create a new first release
12 | - Put the app on elementary AppStore
13 |
14 |
--------------------------------------------------------------------------------
/po/POTFILES:
--------------------------------------------------------------------------------
1 | AudioCutter/application.py
2 | AudioCutter/const.py
3 | AudioCutter/widgets/about.py
4 | AudioCutter/widgets/actionbar.py
5 | AudioCutter/widgets/audio_graph.py
6 | AudioCutter/widgets/headerbar.py
7 | AudioCutter/widgets/notification.py
8 | AudioCutter/widgets/settings.py
9 | AudioCutter/widgets/shortcuts.py
10 | AudioCutter/widgets/soundconfig.py
11 | AudioCutter/widgets/time.py
12 | AudioCutter/widgets/window.py
13 | AudioCutter/widgets/zoombox.py
14 |
--------------------------------------------------------------------------------
/AudioCutter/widgets/loading.py:
--------------------------------------------------------------------------------
1 | from gi import require_version
2 | require_version("Gtk", "3.0")
3 | from gi.repository import Gtk
4 |
5 | class Loading(Gtk.Box):
6 |
7 |
8 | def __init__(self):
9 | Gtk.Box.__init__(self)
10 | self.set_valign(Gtk.Align.CENTER)
11 | self.set_halign(Gtk.Align.CENTER)
12 |
13 | self._spinner = Gtk.Spinner()
14 |
15 | self.add(self._spinner)
16 | self.show_all()
17 |
18 | def start(self):
19 | self._spinner.start()
20 |
21 | def stop(self):
22 | self._spinner.stop()
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: fedora:latest
6 | working_directory: ~/Audio-Cutter
7 | steps:
8 | - run: >
9 | dnf -y install
10 | gettext
11 | gobject-introspection-devel
12 | gtk3-devel
13 | meson
14 | ninja-build
15 | python3-pip
16 | python3-gobject
17 | - checkout
18 | - run: pip3 install pycodestyle
19 | - run: python3 ./tests/test_code_format.py
20 | - run: meson builddir
21 | - run: ninja -C builddir test
22 | - run: ninja -C builddir install
23 |
--------------------------------------------------------------------------------
/data/style.css:
--------------------------------------------------------------------------------
1 | @define-color row_border_color mix(@theme_bg_color, @theme_fg_color, 0.2);
2 | .config-list-box-row {
3 | padding: 8px;
4 | border-top: 1px solid @row_border_color;
5 | border-left: 1px solid @row_border_color;
6 | border-right: 1px solid @row_border_color;
7 | }
8 |
9 | .config-list-box:nth-last-child(2n),
10 | .config-list-box:last-child {
11 | border-bottom: 1px solid @row_border_color;
12 | }
13 |
14 | .entry-error {
15 | color: red;
16 | }
17 |
18 | .audio-graph-container {
19 | background-color: shade(@theme_bg_color, 0.95);
20 | border-bottom: 1px @row_border_color solid;
21 | }
22 |
--------------------------------------------------------------------------------
/data/icons/meson.build:
--------------------------------------------------------------------------------
1 | icon_themes = ['hicolor']
2 | foreach theme : icon_themes
3 | scalable_dir = join_paths(theme, 'scalable/apps')
4 | scalable_icon = join_paths(scalable_dir, meson.project_name() + '.svg')
5 | dest = join_paths(get_option('prefix'), 'share/icons', scalable_dir)
6 | install_data(scalable_icon, install_dir: dest, rename: '@0@.svg'.format(application_id))
7 |
8 | symbolic_dir = join_paths(theme, 'symbolic/apps/')
9 | symbolic_icon = join_paths(symbolic_dir, meson.project_name() + '-symbolic.svg')
10 | dest_symbolic = join_paths(get_option('prefix'), 'share/icons', symbolic_dir)
11 | install_data(symbolic_icon,
12 | install_dir: dest_symbolic,
13 | rename: '@0@'.format(application_id) + '-symbolic.svg')
14 | endforeach
15 |
16 |
--------------------------------------------------------------------------------
/AudioCutter/modules/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from .exporter import Exporter
19 | from .log import Logger
20 | from .settings import Settings
21 | from .player import Player
22 |
--------------------------------------------------------------------------------
/AudioCutter/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 |
19 | from .application import Application
20 | from .widgets import HeaderBar, Window
21 | from .const import AUDIO_MIMES
22 | from .objects import Time
23 | from .utils import format_ns
24 |
--------------------------------------------------------------------------------
/data/com.github.bilelmoussaoui.AudioCutter.appdata.xml.in:
--------------------------------------------------------------------------------
1 |
2 |
3 | com.github.bilelmoussaoui.AudioCutter
4 | CC0
5 | GPL-3.0+
6 | Audio Cutter
7 | Audio Cutter
8 |
9 |
Simple Gtk Application to cut your audio files easily. Following GNOME design guildlines
10 |
11 | com.github.bilelmoussaoui.AudioCutter
12 | com.github.bilelmoussaoui.AudioCutter.desktop
13 | https://github.com/bilelmoussaoui/Audio-Cutter
14 | https://github.com/bilelmoussaoui/Audio-Cutter/issues
15 | https://www.paypal.me/BilalELMoussaoui
16 | Bilal Elmoussaoui
17 | bil.elmoussaoui@gmail.com
18 |
19 |
20 |
--------------------------------------------------------------------------------
/data/org.gnome.AudioCutter.gschema.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [0,0]
6 | Default window postiton
7 |
8 | Default window postiton
9 |
10 |
11 |
12 | false
13 | Night mode
14 |
15 | Enable/disable night mode within the application
16 |
17 |
18 |
19 | ""
20 | File path of the latest opened file
21 |
22 | File path of the latest opened file
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/data/meson.build:
--------------------------------------------------------------------------------
1 | gnome = import('gnome')
2 |
3 | message('Compiling resources')
4 |
5 | gnome.compile_resources(
6 | meson.project_name(),
7 | application_id + '.gresource.xml',
8 | gresource_bundle: true,
9 | source_dir: '.',
10 | install_dir: DATA_DIR,
11 | install: true
12 | )
13 |
14 | message('Compiling schemas')
15 | gnome.compile_schemas()
16 | install_data(
17 | application_id + '.gschema.xml',
18 | install_dir: join_paths(get_option('prefix'), 'share/glib-2.0/schemas')
19 | )
20 |
21 | message('Making a .desktop file')
22 | i18n.merge_file(
23 | output: application_id + '.desktop',
24 | input: application_id + '.desktop.in',
25 | po_dir: '../po',
26 | type: 'desktop',
27 | install: true,
28 | install_dir: join_paths(get_option('datadir'), 'applications')
29 | )
30 |
31 | message('Preparing appdata')
32 | i18n.merge_file(
33 | output: application_id + '.appdata.xml',
34 | input: application_id + '.appdata.xml.in',
35 | po_dir: '../po',
36 | install: true,
37 | install_dir: join_paths(get_option('datadir'), 'appdata')
38 | )
39 |
40 | message('Making a list of icons')
41 | subdir('icons')
42 |
--------------------------------------------------------------------------------
/data/com.github.bilelmoussaoui.AudioCutter.gschema.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [0,0]
6 | Default window postiton
7 |
8 | Default window postiton
9 |
10 |
11 |
12 | false
13 | Night mode
14 |
15 | Enable/disable night mode within the application
16 |
17 |
18 |
19 | ""
20 | File path of the latest opened file
21 |
22 | File path of the latest opened file
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/AudioCutter/widgets/shortcuts.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from gi import require_version
19 | require_version("Gtk", "3.0")
20 | from gi.repository import Gtk
21 |
22 |
23 | class ShortcutsWindow(Gtk.ShortcutsWindow):
24 | """Shortcuts Window widget."""
25 |
26 | def __init__(self):
27 | Gtk.Window.__init__(self)
28 |
--------------------------------------------------------------------------------
/AudioCutter/const.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from gettext import gettext as _
19 |
20 | AUDIO_MIMES = {
21 | "audio/ogg": _("OGG Audio"),
22 | "audio/midi": _("MIDI"),
23 | "audio/x-wav": _("Waveform Audio Format"),
24 | "audio/aac": _("AAC Audio"),
25 | "audio/mpeg": _("MP3 Audio"),
26 | "audio/3gpp": _("3GPP Audio Container"),
27 | "audio/3gpp2": _("3GPP2 Audio Container"),
28 | "audio/webm": _("WEBM Audio")
29 | }
30 |
--------------------------------------------------------------------------------
/AudioCutter/modules/exporter.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 |
19 |
20 | class Exporter:
21 |
22 |
23 | def __init__(self, *args, **kwargs):
24 | self._start_time = kwargs.get("start_time", 0)
25 | self._end_time = kwargs.get("end_time", 0)
26 | self._is_fade_in = kwargs.get("is_fade_in", False)
27 | self._is_fade_out = kwargs.get("is_fade_out", False)
28 | self._audio_format = kwargs.get("audio_format", "")
29 | self._audio_path = kwargs.get("path", "")
30 |
31 | def do(self):
32 | pass
--------------------------------------------------------------------------------
/AudioCutter/widgets/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | Licence : The script is released under GPL, uses a modified script
7 | form Chromium project released under BSD license
8 | This file is part of AudioCutter.
9 | AudioCutter is free software: you can redistribute it and/or
10 | modify it under the terms of the GNU General Public License as published
11 | by the Free Software Foundation, either version 3 of the License, or
12 | (at your option) any later version.
13 | AudioCutter is distributed in the hope that it will be useful,
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | GNU General Public License for more details.
17 | You should have received a copy of the GNU General Public License
18 | along with AudioCutter. If not, see .
19 | """
20 |
21 | from .window import Window
22 | from .headerbar import HeaderBar
23 | from .soundconfig import SoundConfig
24 | from .actionbar import ActionBar
25 | from .about import AboutDialog
26 | from .notification import Notification
27 | from .shortcuts import ShortcutsWindow
28 | from .settings import SettingsWindow
29 | from .time import TimeButton
30 | from .audio_graph import AudioGraph
31 | from .zoombox import ZoomBox
32 | from .loading import Loading
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Audio Cutter
2 |
3 | [](https://circleci.com/gh/bilelmoussaoui/Audio-Cutter/tree/master)
4 |
5 | The project is a WIP.
6 |
7 | ## Screenshots
8 |
9 |
10 |
11 |
12 | ## How to install?
13 | As the application is a WIP, there are no stable packages of the application yet. Once the first stable release is out, you should be able to get it from Flathub.
14 |
15 | ### Dependencies
16 | - `python3`
17 | - `gtk3`
18 | - `gstreamer`
19 | - `gstreamer-plugins-good`
20 | - `gst-editing-services`
21 | - `gst-transcoder`
22 | - `gst-python`
23 | - `cairo`
24 | - `py3cairo`
25 | - `python-numpy`
26 | - `meson`
27 |
28 | ### Install the Flatpak nightly
29 | In order to build and run the Flatpak package you need to install
30 | - `xdg-desktop-portal`
31 | - `xdg-desktop-portal-gtk`
32 |
33 | The GNOME 3.30 runtime/sdk is required to build the Flatpak package
34 | ```
35 | flatpak install flathub org.gnome.Sdk//3.30
36 | ```
37 | Then you can build & install it
38 | ```
39 | git clone https://github.com/bilelmoussaoui/Audio-Cutter.git
40 | cd Audio-Cutter/build-aux/flatpak
41 | flatpak-builder --install app com.github.bilelmoussaoui.AudioCutter.json --user
42 | ```
43 | You can run it using
44 | ```
45 | flatpak run com.github.bilelmoussaoui.AudioCutter
46 | ```
47 | ### Manual installation
48 | ```
49 | meson _build --prefix=/usr
50 | sudo ninja -C _build install
51 | ```
52 |
--------------------------------------------------------------------------------
/AudioCutter/utils.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from .objects import Time
19 | from hashlib import sha256
20 | from os import path, makedirs
21 | from gi.repository import GLib
22 |
23 |
24 | def get_wavefile_location_for_uri(uri):
25 | filename = sha256(uri.encode("utf-8")).hexdigest()
26 | cachedir = path.join(GLib.get_user_cache_dir(), "AudioCutter")
27 | if not path.exists(cachedir):
28 | makedirs(cachedir)
29 | return path.join(cachedir, filename)
30 |
31 |
32 |
33 | def format_ns(nanoseconds):
34 | """
35 | Convert nano seconds to a Time object.
36 | Original code:
37 | https://github.com/gkralik/python-gst-tutorial/blob/master/helper.py
38 | """
39 | seconds, nanoseconds = divmod(nanoseconds, 1000000000)
40 | minutes, seconds = divmod(seconds, 60)
41 | hours, minutes = divmod(minutes, 60)
42 | return Time(hours, minutes, seconds)
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | builddir/
3 | _build
4 | # Byte-compiled / optimized / DLL files
5 | __pycache__/
6 | *.py[cod]
7 | *$py.class
8 | .flatpak-builder/
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | env/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | wheels/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | .hypothesis/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 | local_settings.py
59 |
60 | # Flask stuff:
61 | instance/
62 | .webassets-cache
63 |
64 | # Scrapy stuff:
65 | .scrapy
66 |
67 | # Sphinx documentation
68 | docs/_build/
69 |
70 | # PyBuilder
71 | target/
72 |
73 | # Jupyter Notebook
74 | .ipynb_checkpoints
75 |
76 | # pyenv
77 | .python-version
78 |
79 | # celery beat schedule file
80 | celerybeat-schedule
81 |
82 | # SageMath parsed files
83 | *.sage.py
84 |
85 | # dotenv
86 | .env
87 |
88 | # virtualenv
89 | .venv
90 | venv/
91 | ENV/
92 |
93 | # Spyder project settings
94 | .spyderproject
95 | .spyproject
96 |
97 | # Rope project settings
98 | .ropeproject
99 |
100 | # mkdocs documentation
101 | /site
102 |
103 | # mypy
104 | .mypy_cache/
105 | >>>>>>> 99f662c5e5d4d980c977d707f480779648453417
106 |
--------------------------------------------------------------------------------
/tests/test_code_format.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | Licence : The script is released under GPL, uses a modified script
7 | form Chromium project released under BSD license
8 | This file is part of AudioCutter.
9 | AudioCutter is free software: you can redistribute it and/or
10 | modify it under the terms of the GNU General Public License as published
11 | by the Free Software Foundation, either version 3 of the License, or
12 | (at your option) any later version.
13 | AudioCutter is distributed in the hope that it will be useful,
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | GNU General Public License for more details.
17 | You should have received a copy of the GNU General Public License
18 | along with AudioCutter. If not, see .
19 | """
20 | import unittest
21 | from os import path
22 | from glob import glob
23 |
24 | import pycodestyle
25 |
26 | ABS_PATH = path.abspath(path.join(path.dirname(path.abspath(__file__)),
27 | "../"))
28 |
29 |
30 | class TestCodeFormat(unittest.TestCase):
31 | """Test Code format using pep8."""
32 |
33 | def setUp(self):
34 | self.style = pycodestyle.StyleGuide(show_source=True,
35 | ignore="E402")
36 |
37 | def test_code_format(self):
38 | """Test code format."""
39 |
40 | files = glob("{}/**/*.py".format(ABS_PATH))
41 | result = self.style.check_files(files)
42 | self.assertEqual(result.total_errors, 0,
43 | "Found code style errors (and warnings).")
44 |
45 |
46 | if __name__ == "__main__":
47 | unittest.main()
48 |
--------------------------------------------------------------------------------
/AudioCutter/widgets/about.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from gettext import gettext as _
19 |
20 | from gi import require_version
21 | require_version("Gtk", "3.0")
22 | from gi.repository import Gtk
23 |
24 |
25 | class AboutDialog(Gtk.AboutDialog):
26 | """About Dialog widget."""
27 |
28 | def __init__(self):
29 | Gtk.AboutDialog.__init__(self)
30 | self.set_modal(True)
31 | self._setup_widget()
32 |
33 | def _setup_widget(self):
34 | """Setup the about dialog widget."""
35 | self.set_authors(["Bilal Elmoussaoui"])
36 | self.set_artists(["Alfredo Hernández"])
37 | self.set_logo_icon_name("com.github.bilelmoussaoui.AudioCutter")
38 | self.set_license_type(Gtk.License.GPL_3_0)
39 | self.set_program_name(_("Audio Cutter"))
40 | self.set_translator_credits(_("translator-credits"))
41 | self.set_version("0.1")
42 | self.set_comments(_("Cut your audio files like no one does"))
43 | self.set_website("https://github.com/bil-elmoussaoui/Audio-Cutter")
44 |
--------------------------------------------------------------------------------
/audio-cutter.py.in:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """
3 | Your favorite Audio Cutter.
4 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
5 | Artist : Alfredo Hernández
6 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
7 | This file is part of AudioCutter.
8 | AudioCutter is free software: you can redistribute it and/or
9 | modify it under the terms of the GNU General Public License as published
10 | by the Free Software Foundation, either version 3 of the License, or
11 | (at your option) any later version.
12 | AudioCutter is distributed in the hope that it will be useful,
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | GNU General Public License for more details.
16 | You should have received a copy of the GNU General Public License
17 | along with AudioCutter. If not, see .
18 | """
19 |
20 | import gettext
21 | import locale
22 | import sys
23 | from os import path
24 |
25 | sys.path.insert(1, '@PYTHON_DIR@')
26 | sys.path.append(path.join("@LIBDIR@", 'AudioCutter/python'))
27 |
28 | from gi import require_version
29 | require_version("GES", "1.0")
30 | from gi.repository import Gio, GES, Gst
31 |
32 | if __name__ == "__main__":
33 |
34 | locale.bindtextdomain('audiocutter', '@LOCALE_DIR@')
35 | locale.textdomain('audiocutter')
36 | gettext.bindtextdomain('audiocutter', '@LOCALE_DIR@')
37 | gettext.textdomain('audiocutter')
38 |
39 | resource = Gio.resource_load(path.join('@DATA_DIR@',
40 | '@PROJECT_NAME@.gresource'))
41 | Gio.Resource._register(resource)
42 |
43 | Gst.init(sys.argv)
44 | res, sys.argv = GES.init_check(sys.argv)
45 |
46 | from AudioCutter.application import Application
47 | try:
48 | app = Application()
49 | exit_status = app.run(sys.argv)
50 | sys.exit(exit_status)
51 | except KeyboardInterrupt:
52 | exit()
53 |
--------------------------------------------------------------------------------
/AudioCutter/widgets/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from gettext import gettext as _
19 |
20 | from gi import require_version
21 | require_version("Gtk", "3.0")
22 | from gi.repository import Gtk
23 |
24 |
25 | class SettingRow(Gtk.ListBoxRow):
26 |
27 | def __init__(self, label, widget):
28 | Gtk.ListBoxRow.__init__(self)
29 | self.label = label
30 | self.widget = widget
31 |
32 | self._build_widgets()
33 |
34 | def _build_widgets(self):
35 | label = Gtk.Label(label=self.label)
36 |
37 |
38 |
39 | class SettingsWindow(Gtk.Window):
40 | """Settings window widget."""
41 |
42 | def __init__(self):
43 | Gtk.Window.__init__(self)
44 | self.set_resizable(False)
45 | self.set_size_request(500, 400)
46 | self.resize(500, 400)
47 | self._build_widgets()
48 |
49 | def show_window(self):
50 | """Show the current settings window."""
51 | self.show_all()
52 |
53 | def _build_widgets(self):
54 | """Build Settings Window widgets."""
55 | # HeaderBar
56 | headerbar = Gtk.HeaderBar()
57 | headerbar.set_show_close_button(True)
58 | headerbar.set_title(_("Settings"))
59 | self.set_titlebar(headerbar)
60 |
--------------------------------------------------------------------------------
/AudioCutter/widgets/zoombox.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 |
19 | from gi import require_version
20 | require_version("Gtk", "3.0")
21 | from gi.repository import Gtk, Gio
22 |
23 |
24 | class ZoomBox(Gtk.Box):
25 |
26 | def __init__(self):
27 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
28 | self.set_valign(Gtk.Align.START)
29 | self.set_halign(Gtk.Align.END)
30 | self.zoom_up = Gtk.Button()
31 | self.zoom_down = Gtk.Button()
32 | self._build_widgets()
33 |
34 |
35 | def _build_widgets(self):
36 | up_icn = Gio.ThemedIcon(name="list-add-symbolic")
37 | up_img = Gtk.Image.new_from_gicon(up_icn, Gtk.IconSize.BUTTON)
38 | self.zoom_up.set_image(up_img)
39 | self.zoom_up.get_style_context().add_class("flat")
40 |
41 | # Lower btn
42 | lower_icn = Gio.ThemedIcon(name="list-remove-symbolic")
43 | lower_img = Gtk.Image.new_from_gicon(lower_icn, Gtk.IconSize.BUTTON)
44 | self.zoom_down.set_image(lower_img)
45 | self.zoom_down.get_style_context().add_class("flat")
46 |
47 | self.pack_start(self.zoom_up, False, False, 0)
48 | self.pack_start(self.zoom_down, False, False, 0)
49 | self.show_all()
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project('com.github.bilelmoussaoui.AudioCutter', 'c',
2 | version: '0.1',
3 | meson_version: '>= 0.48',
4 | license: 'GPL-3.0',
5 | default_options: ['prefix=/usr']
6 | )
7 | application_id = 'com.github.bilelmoussaoui.AudioCutter'
8 |
9 | i18n = import('i18n')
10 | python = import('python3')
11 |
12 | message('Looking for dependencies')
13 | python_bin = python.find_python()
14 | if not python_bin.found()
15 | error('No valid python3 binary found')
16 | endif
17 | dependency('glib-2.0')
18 | dependency('gobject-2.0')
19 | dependency('gobject-introspection-1.0')
20 | dependency('gtk+-3.0', version :'>=3.16')
21 |
22 | BIN_DIR = join_paths(get_option('prefix'), get_option('bindir'))
23 | PYTHON_DIR = join_paths(get_option('prefix'), python.sysconfig_path('purelib'))
24 | DATA_DIR = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
25 | LOCALE_DIR = join_paths(get_option('prefix'), get_option('datadir'), 'locale')
26 |
27 | conf = configuration_data()
28 | conf.set('DATA_DIR', DATA_DIR)
29 | conf.set('LOCALE_DIR', LOCALE_DIR)
30 | conf.set('PYTHON_DIR', PYTHON_DIR)
31 | conf.set('VERSION', meson.project_version())
32 | conf.set('PROJECT_NAME', meson.project_name())
33 | conf.set('LIBDIR', join_paths(get_option('prefix'), get_option('libdir')))
34 |
35 | subdir('data')
36 | subdir('po')
37 |
38 | python_dep = dependency('python3', version : '>= 3.3')
39 | gst_dep = dependency('gstreamer-1.0', version : '>= 1.12')
40 | cairo_dep = dependency('cairo')
41 | pycairo_dep = dependency('py3cairo')
42 |
43 | shared_library(
44 | 'renderer',
45 | 'AudioCutter/objects/renderer.c',
46 | dependencies: [
47 | gst_dep,
48 | python_dep,
49 | pycairo_dep,
50 | cairo_dep
51 | ],
52 | name_prefix : '',
53 | install: true,
54 | install_dir: join_paths(get_option('libdir') + '/AudioCutter/python')
55 | )
56 |
57 | install_subdir(
58 | 'AudioCutter',
59 | install_dir: PYTHON_DIR
60 | )
61 |
62 | configure_file(
63 | input: 'audio-cutter.py.in',
64 | output: 'audio-cutter',
65 | configuration: conf,
66 | install_dir: BIN_DIR
67 | )
68 |
69 | meson.add_install_script('build-aux/meson/meson_post_install.py')
70 |
--------------------------------------------------------------------------------
/AudioCutter/modules/log.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | import logging
19 |
20 |
21 | class Logger:
22 | """Logging handler."""
23 | # Default instance of Logger
24 | instance = None
25 | # Message format
26 | FORMAT = "[%(levelname)-s] %(asctime)s %(message)s"
27 | # Date format
28 | DATE = "%Y-%m-%d %H:%M:%S"
29 |
30 | def __init__(self):
31 | logger = logging.getLogger('audio-cutter')
32 | handler = logging.StreamHandler()
33 | formater = logging.Formatter(Logger.FORMAT, Logger.DATE)
34 | handler.setFormatter(formater)
35 | logger.setLevel(logging.DEBUG)
36 | logger.addHandler(handler)
37 |
38 | @staticmethod
39 | def get_default():
40 | """Return the default instance of Logger."""
41 | if Logger.instance is None:
42 | Logger.instance = logging.getLogger("audio-cutter")
43 | return Logger.instance
44 |
45 | @staticmethod
46 | def warning(msg):
47 | """Log a warning message."""
48 | Logger.get_default().warning(msg)
49 |
50 | @staticmethod
51 | def debug(msg):
52 | """Log a debug message."""
53 | Logger.get_default().debug(msg)
54 |
55 | @staticmethod
56 | def info(msg):
57 | """Log an info message."""
58 | Logger.get_default().info(msg)
59 |
60 | @staticmethod
61 | def error(msg):
62 | """Log an error message."""
63 | Logger.get_default().error(msg)
64 |
--------------------------------------------------------------------------------
/AudioCutter/widgets/notification.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 |
19 | from gi import require_version
20 | require_version("Gtk", "3.0")
21 |
22 | from gi.repository import Gtk
23 |
24 | class Notification(Gtk.Revealer):
25 | instance = None
26 | def __init__(self):
27 | Gtk.Revealer.__init__(self)
28 | self._message = None
29 | self._message_label = Gtk.Label()
30 | self.set_reveal_child(False)
31 | self.set_transition_type(Gtk.RevealerTransitionType.SLIDE_DOWN)
32 | self._build_widget()
33 |
34 | @property
35 | def message(self):
36 | """Return the message value:str."""
37 | return self._message
38 |
39 | @message.setter
40 | def message(self, new_msg):
41 | self._message = new_msg
42 | self._message_label.set_label(new_msg)
43 | self.set_reveal_child(True)
44 |
45 |
46 | @staticmethod
47 | def get_default():
48 | """Return the default instance of Notification."""
49 | if Notification.instance is None:
50 | Notification.instance = Notification()
51 | return Notification.instance
52 |
53 |
54 | def _build_widget(self):
55 | container = Gtk.InfoBar()
56 | container.set_show_close_button(True)
57 | container.get_content_area().pack_start(self._message_label, False,
58 | False, 0)
59 | container.connect("response", self._on_infobar_response)
60 |
61 | self.add(container)
62 |
63 | def _on_infobar_response(self, infobar, response_id):
64 | if response_id == Gtk.ResponseType.CLOSE:
65 | self.set_reveal_child(False)
66 |
--------------------------------------------------------------------------------
/AudioCutter/objects.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from copy import copy
19 |
20 |
21 | class Time:
22 |
23 | def __init__(self, hours, minutes, seconds):
24 | self.hours = hours
25 | self.minutes = minutes
26 | self.seconds = seconds
27 |
28 | def __eq__(self, time):
29 | return (time.minutes == self.minutes and
30 | time.seconds == self.seconds and
31 | time.hours == self.hours)
32 |
33 | def copy(self):
34 | """Return a copy of the object."""
35 | return copy(self)
36 |
37 | def __ge__(self, time):
38 | return self.total > time.total
39 |
40 | def __le__(self, time):
41 | return self.total < time.total
42 |
43 | def __repr__(self):
44 | return("Hours: {} Minutes: {} Seconds: {}".format(self.hours,
45 | self.minutes,
46 | self.seconds))
47 |
48 | def up(self):
49 | seconds_ = self.seconds + 1
50 | if seconds_ >= 60:
51 | self.seconds = 0
52 | self.minutes += 1
53 | if self.minutes > 60:
54 | self.minutes = 0
55 | self.hours += 1
56 | else:
57 | self.seconds = seconds_
58 |
59 | def down(self):
60 | seconds_ = self.seconds - 1
61 | if seconds_ < 0:
62 | self.seconds = 0
63 | self.minutes -= 1
64 | if self.minutes < 0:
65 | self.minutes = 0
66 | self.hours -= 1
67 | elif seconds_ == 0:
68 | self.seconds = 59
69 | self.minutes -= 1
70 | if self.minutes < 0:
71 | self.minutes = 0
72 | self.hours -= 1
73 | else:
74 | self.seconds = seconds_
75 |
76 | @property
77 | def total(self):
78 | return self.hours * 3600 + self.minutes * 60 + self.seconds
79 |
--------------------------------------------------------------------------------
/data/icons/hicolor/symbolic/apps/com.github.bilelmoussaoui.AudioCutter-symbolic.svg:
--------------------------------------------------------------------------------
1 |
2 |
62 |
--------------------------------------------------------------------------------
/AudioCutter/widgets/actionbar.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 |
19 | from gettext import gettext as _
20 | from ..const import AUDIO_MIMES
21 | from gi import require_version
22 | require_version("Gtk", "3.0")
23 | from gi.repository import Gtk, GObject
24 |
25 |
26 | class ActionBar(Gtk.ActionBar, GObject.GObject):
27 | """ActionBar widget."""
28 | # ToolBar Instance
29 | instance = None
30 | __gsignals__ = {
31 | 'selected-format': (GObject.SignalFlags.RUN_FIRST, None, (str, ))
32 | }
33 |
34 | def __init__(self):
35 | GObject.GObject.__init__(self)
36 | Gtk.ActionBar.__init__(self)
37 | self.set_border_width(12)
38 | self._save_btn = Gtk.Button()
39 | self._output_format = Gtk.ComboBox()
40 | self._setup_widgets()
41 |
42 | @staticmethod
43 | def get_default():
44 | """Return the default isntance on ActionBar."""
45 | if ActionBar.instance is None:
46 | ActionBar.instance = ActionBar()
47 | return ActionBar.instance
48 |
49 | def _setup_widgets(self):
50 | """Create/Setup the main widgets of the ActionBar."""
51 | # Save Button
52 | self._save_btn.set_label(_("Save"))
53 | self._save_btn.connect("clicked", self._on_save)
54 | self._save_btn.get_style_context().add_class("suggested-action")
55 | self._save_btn.set_sensitive(False)
56 | self.pack_end(self._save_btn)
57 | # Output format Combo box
58 | model = Gtk.ListStore(str, str)
59 | for mimetype, desc in AUDIO_MIMES.items():
60 | model.append([desc, mimetype])
61 | renderer_text = Gtk.CellRendererText()
62 | self._output_format.pack_start(renderer_text, True)
63 | self._output_format.add_attribute(renderer_text, "text", 0)
64 | self._output_format.set_active(0)
65 | self._output_format.set_model(model)
66 | self._output_format.set_sensitive(False)
67 | self.pack_end(self._output_format)
68 |
69 | def set_state(self, state):
70 | """Set the ActionBar as active/inactive."""
71 | self._save_btn.set_sensitive(state)
72 | self._output_format.set_sensitive(state)
73 |
74 | def _on_save(self, button):
75 | active_id = self._output_format.get_active()
76 | audio_mimes_keys = list(AUDIO_MIMES.keys())
77 | try:
78 | output_format = audio_mimes_keys[active_id]
79 | except KeyError:
80 | output_format = audio_mimes_keys[0]
81 | self.emit("selected-format", output_format)
--------------------------------------------------------------------------------
/AudioCutter/modules/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from gi.repository import Gio, GLib
19 | from . import Logger
20 |
21 |
22 | class Settings(Gio.Settings):
23 | """Gio.Settings handler."""
24 | # Default instance of Settings
25 | instance = None
26 |
27 | def __init__(self):
28 | Gio.Settings.__init__(self)
29 |
30 | def new():
31 | """Create a new instance of Gio.Settings."""
32 | gsettings = Gio.Settings.new("com.github.bilelmoussaoui.AudioCutter")
33 | gsettings.__class__ = Settings
34 | return gsettings
35 |
36 | @staticmethod
37 | def get_default():
38 | """Return the default instance of Settings."""
39 | if Settings.instance is None:
40 | Settings.instance = Settings.new()
41 | return Settings.instance
42 |
43 | @property
44 | def window_position(self):
45 | """Return a tuple (x, y) for the window's latest position."""
46 | window_position = tuple(self.get_value('window-position'))
47 | Logger.debug("[Settings] Window position: "
48 | "{}".format(list(window_position)))
49 | return window_position
50 |
51 | @window_position.setter
52 | def window_position(self, position):
53 | """Set the window position."""
54 | position = GLib.Variant('ai', list(position))
55 | Logger.debug("[Settings] Window position is set to: "
56 | "{}".format(list(position)))
57 | self.set_value('window-position', position)
58 |
59 | @property
60 | def is_night_mode(self):
61 | """Return if the night mode is on or off."""
62 | is_night_mode = self.get_boolean('night-mode')
63 | Logger.debug("[Settings] Night mode: "
64 | "{}".format(str(is_night_mode)))
65 | return is_night_mode
66 |
67 | @is_night_mode.setter
68 | def is_night_mode(self, status):
69 | """Switch the night mode."""
70 | Logger.debug("[Settings] Night mode is set to: "
71 | "{}".format(str(status)))
72 | self.set_boolean('night-mode', status)
73 |
74 | @property
75 | def last_file(self):
76 | """Return the latest opened file path."""
77 | last_file = self.get_string('last-file')
78 | Logger.debug("[Settings] Last opened file: "
79 | "{}".format(last_file))
80 | return last_file
81 |
82 | @last_file.setter
83 | def last_file(self, last_file):
84 | self.set_string('last-file', last_file)
85 | Logger.debug("[Settings] Last opened file is set to: "
86 | "{}".format(last_file))
87 |
--------------------------------------------------------------------------------
/AudioCutter/widgets/headerbar.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from gettext import gettext as _
19 | from os import path
20 |
21 | from gi import require_version
22 | require_version("Gtk", "3.0")
23 | from gi.repository import Gio, GObject, Gtk
24 |
25 |
26 | class HeaderBar(Gtk.HeaderBar, GObject.GObject):
27 | """Main Window's Header Bar widget."""
28 |
29 | # HeaderBar Instance
30 | instance = None
31 |
32 | # GObject signals
33 | __gsignals__ = {
34 | 'open-file': (GObject.SignalFlags.RUN_FIRST, None, ())
35 | }
36 |
37 | def __init__(self):
38 | GObject.GObject.__init__(self)
39 | Gtk.HeaderBar.__init__(self)
40 | self.play_btn = Gtk.Button()
41 | self._open_btn = Gtk.Button()
42 | self.menu_btn = Gtk.Button()
43 | self.set_title(_("Audio Cutter"))
44 | self.set_show_close_button(True)
45 | self._setup_widgets()
46 |
47 | @staticmethod
48 | def get_default():
49 | """Return the default instnace of HeaderBar."""
50 | if HeaderBar.instance is None:
51 | HeaderBar.instance = HeaderBar()
52 | return HeaderBar.instance
53 |
54 | def _setup_widgets(self):
55 | """Setup main headerbar widgets."""
56 | # Open button
57 | self._open_btn.set_label(_("Open"))
58 | self._open_btn.connect("clicked", self._open_file)
59 | self.pack_start(self._open_btn)
60 |
61 | menu_icn = Gio.ThemedIcon(name="open-menu-symbolic")
62 | menu_img = Gtk.Image.new_from_gicon(menu_icn, Gtk.IconSize.BUTTON)
63 | self.menu_btn.set_image(menu_img)
64 | self.pack_end(self.menu_btn)
65 |
66 | # Play Button
67 | self.set_is_playing(False)
68 | self.play_btn.set_sensitive(False)
69 | self.pack_start(self.play_btn)
70 |
71 | def _open_file(self, *args):
72 | """Send a open-file signal to the Main Window."""
73 | self.emit("open-file")
74 |
75 | def set_audio_title(self, title):
76 | """
77 | Set a filename as open.
78 | Change the subtitle to the filename.
79 | Also makes the play button sensitive
80 | """
81 | self.set_subtitle(title)
82 | self.play_btn.set_sensitive(True)
83 | self.set_has_subtitle(True)
84 |
85 | def set_is_playing(self, is_playing):
86 | """Set the play button info depending on the player status."""
87 | if not is_playing:
88 | tooltip = _("Play")
89 | icon_name = "media-playback-start-symbolic"
90 | else:
91 | tooltip = _("Pause")
92 | icon_name = "media-playback-pause-symbolic"
93 | icon = Gio.ThemedIcon(name=icon_name)
94 | image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON)
95 | self.play_btn.set_image(image)
96 | self.play_btn.set_tooltip_text(tooltip)
97 |
--------------------------------------------------------------------------------
/AudioCutter/objects/renderer.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | static GObjectClass * gobject_class;
8 |
9 | /*
10 | * This function must be called with a range of samples, and a desired
11 | * width and height.
12 | * It will average samples if needed.
13 | */
14 | static PyObject *
15 | py_fill_surface (PyObject * self, PyObject * args)
16 | {
17 | PyObject *samples;
18 | PyObject *sampleObj;
19 | int length, i;
20 | double sample;
21 | cairo_surface_t *surface;
22 | cairo_t *ctx;
23 | int width, height;
24 | float pixelsPerSample;
25 | float currentPixel;
26 | int samplesInAccum;
27 | float x = 0.;
28 | double accum;
29 |
30 | if (!PyArg_ParseTuple (args, "O!ii", &PyList_Type, &samples, &width, &height))
31 | return NULL;
32 |
33 | length = PyList_Size (samples);
34 |
35 | surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
36 |
37 | ctx = cairo_create (surface);
38 |
39 | cairo_set_source_rgb (ctx, 0.2, 0.56, 0.88);
40 | cairo_set_line_width (ctx, 0.2);
41 | cairo_move_to (ctx, 0, height);
42 |
43 | pixelsPerSample = width / (float) length;
44 | currentPixel = 0.;
45 | samplesInAccum = 0;
46 | accum = 0.;
47 |
48 | for (i = 0; i < length; i++) {
49 | /* Guaranteed to return something */
50 | sampleObj = PyList_GetItem (samples, i);
51 | sample = PyFloat_AsDouble (sampleObj);
52 |
53 | /* If the object was not a float or convertible to float */
54 | if (PyErr_Occurred ()) {
55 | cairo_surface_finish (surface);
56 | Py_DECREF (samples);
57 | return NULL;
58 | }
59 |
60 | currentPixel += pixelsPerSample;
61 | samplesInAccum += 1;
62 | accum += sample;
63 | if (currentPixel > 1.0) {
64 | accum /= samplesInAccum;
65 | cairo_line_to (ctx, x, height - accum);
66 | accum = 0;
67 | currentPixel -= 1.0;
68 | samplesInAccum = 0;
69 | }
70 | x += pixelsPerSample;
71 | }
72 |
73 | Py_DECREF (samples);
74 | cairo_line_to (ctx, width, height);
75 | cairo_close_path (ctx);
76 | cairo_fill_preserve (ctx);
77 |
78 | return PycairoSurface_FromSurface (surface, NULL);
79 | }
80 |
81 | static PyMethodDef renderer_methods[] = {
82 | {"fill_surface", py_fill_surface, METH_VARARGS},
83 | {NULL, NULL}
84 | };
85 |
86 | static PyModuleDef module = {
87 | PyModuleDef_HEAD_INIT,
88 | "renderer",
89 | "Pitivi renderer module.",
90 | -1,
91 | renderer_methods, NULL, NULL, NULL, NULL
92 | };
93 |
94 | static void
95 | pitivi_disable_gst_object_dispatch_properties_changed (GObject * object,
96 | guint n_pspecs, GParamSpec ** pspecs)
97 | {
98 | GST_DEBUG_OBJECT (object, "Disabling `deep-notify`");
99 |
100 | gobject_class->dispatch_properties_changed (object, n_pspecs, pspecs);
101 | }
102 |
103 | static void
104 | _disable_gst_object_deep_notify_recurse (GType type)
105 | {
106 | gint i;
107 | GType *types;
108 | GObjectClass *klass = g_type_class_ref (type);
109 |
110 | klass->dispatch_properties_changed =
111 | pitivi_disable_gst_object_dispatch_properties_changed;
112 | g_type_class_unref (klass);
113 |
114 | types = g_type_children (type, NULL);
115 | for (i=0; types[i]; i++)
116 | _disable_gst_object_deep_notify_recurse (types[i]);
117 |
118 | }
119 |
120 | PyMODINIT_FUNC
121 | PyInit_renderer (void)
122 | {
123 | PyObject *m;
124 |
125 | gobject_class = g_type_class_peek (G_TYPE_OBJECT);
126 |
127 | /* Workaround https://phabricator.freedesktop.org/T3350 */
128 | _disable_gst_object_deep_notify_recurse (GST_TYPE_OBJECT);
129 |
130 | if (import_cairo () < 0) {
131 | g_print ("Cairo import failed.");
132 | }
133 |
134 | m = PyModule_Create (&module);
135 | if (m == NULL)
136 | return NULL;
137 | return m;
138 | }
139 |
--------------------------------------------------------------------------------
/po/fr.po:
--------------------------------------------------------------------------------
1 | # French translations for audiocutter package.
2 | # Copyright (C) 2019 THE audiocutter'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the audiocutter package.
4 | # Automatically generated, 2019.
5 | #
6 | msgid ""
7 | msgstr ""
8 | "Project-Id-Version: audiocutter\n"
9 | "Report-Msgid-Bugs-To: \n"
10 | "POT-Creation-Date: 2019-01-22 18:32+0100\n"
11 | "PO-Revision-Date: 2019-01-22 18:32+0100\n"
12 | "Last-Translator: Automatically generated\n"
13 | "Language-Team: none\n"
14 | "Language: fr\n"
15 | "MIME-Version: 1.0\n"
16 | "Content-Type: text/plain; charset=UTF-8\n"
17 | "Content-Transfer-Encoding: 8bit\n"
18 | "Plural-Forms: nplurals=2; plural=(n > 1);\n"
19 |
20 | #: AudioCutter/application.py:38 AudioCutter/widgets/about.py:39
21 | #: AudioCutter/widgets/headerbar.py:43
22 | msgid "Audio Cutter"
23 | msgstr "Audio Cutter"
24 |
25 | #: AudioCutter/application.py:78
26 | msgid "Night Mode"
27 | msgstr "Mode nuit"
28 |
29 | #: AudioCutter/application.py:81
30 | msgid "Keyboard Shortcuts"
31 | msgstr "Raccourcis clavier"
32 |
33 | #: AudioCutter/application.py:84
34 | msgid "About Audio Cutter"
35 | msgstr "À propos d'Audio Cutter"
36 |
37 | #: AudioCutter/const.py:21
38 | msgid "OGG Audio"
39 | msgstr "Audio OGG"
40 |
41 | #: AudioCutter/const.py:22
42 | msgid "MIDI"
43 | msgstr "MIDI"
44 |
45 | #: AudioCutter/const.py:23
46 | msgid "Waveform Audio Format"
47 | msgstr "Audio Waveform (WAV)"
48 |
49 | #: AudioCutter/const.py:24
50 | msgid "AAC Audio"
51 | msgstr "Audio AAC"
52 |
53 | #: AudioCutter/const.py:25
54 | msgid "MP3 Audio"
55 | msgstr "Audio MP3"
56 |
57 | #: AudioCutter/const.py:26
58 | msgid "3GPP Audio Container"
59 | msgstr "Conteneur audio 3GPP"
60 |
61 | #: AudioCutter/const.py:27
62 | msgid "3GPP2 Audio Container"
63 | msgstr "Conteneur audio 3GPP2"
64 |
65 | #: AudioCutter/const.py:28
66 | msgid "WEBM Audio"
67 | msgstr "Audio Webm"
68 |
69 | #: AudioCutter/widgets/about.py:40
70 | msgid "translator-credits"
71 | msgstr "Romain F. T."
72 |
73 | #: AudioCutter/widgets/about.py:42
74 | msgid "Cut your audio files like no one does"
75 | msgstr "Découpez vos fichiers audio mieux que personne"
76 |
77 | #. Save Button
78 | #: AudioCutter/widgets/actionbar.py:52
79 | msgid "Save"
80 | msgstr "Enregistrer"
81 |
82 | #. Open button
83 | #: AudioCutter/widgets/headerbar.py:57 AudioCutter/widgets/window.py:125
84 | msgid "Open"
85 | msgstr "Ouvrir"
86 |
87 | #: AudioCutter/widgets/headerbar.py:88
88 | msgid "Play"
89 | msgstr "Lecture"
90 |
91 | #: AudioCutter/widgets/headerbar.py:91
92 | msgid "Pause"
93 | msgstr "Pause"
94 |
95 | #: AudioCutter/widgets/settings.py:58
96 | msgid "Settings"
97 | msgstr "Paramètres"
98 |
99 | #: AudioCutter/widgets/soundconfig.py:74
100 | msgid "Start time"
101 | msgstr "Temps de début"
102 |
103 | #: AudioCutter/widgets/soundconfig.py:75
104 | msgid "Fade in"
105 | msgstr "Fondu en ouverture"
106 |
107 | #: AudioCutter/widgets/soundconfig.py:82
108 | msgid "End time"
109 | msgstr "Temps de fin"
110 |
111 | #: AudioCutter/widgets/soundconfig.py:83
112 | msgid "Fade out"
113 | msgstr "Fondu en fermeture"
114 |
115 | #: AudioCutter/widgets/time.py:127
116 | msgid "Hours should be less than 24"
117 | msgstr "Les heures doivent être moins de 24"
118 |
119 | #: AudioCutter/widgets/time.py:129
120 | msgid "Minutes must be less than 60"
121 | msgstr "Les minutes doivent être moins de 60"
122 |
123 | #: AudioCutter/widgets/time.py:131
124 | msgid "Seconds must be less than 60"
125 | msgstr "Les secondes doivent être moins de 60"
126 |
127 | #: AudioCutter/widgets/time.py:133 AudioCutter/widgets/time.py:135
128 | msgid "Invalid time format, please follow hh:mm:ss"
129 | msgstr "Format de temps invalide, veuillez respecter hh:mm:ss"
130 |
131 | #: AudioCutter/widgets/window.py:126
132 | msgid "Cancel"
133 | msgstr "Annuler"
134 |
135 | #: AudioCutter/widgets/window.py:143
136 | msgid "Audio Files"
137 | msgstr "Fichiers audio"
138 |
--------------------------------------------------------------------------------
/po/ru.po:
--------------------------------------------------------------------------------
1 | # French translations for audiocutter package.
2 | # Copyright (C) 2019 THE audiocutter'S COPYRIGHT HOLDER
3 | # This file is distributed under the same license as the audiocutter package.
4 | # Automatically generated, 2019.
5 | # Dead Mozay , 2019.
6 | #
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: audiocutter\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2019-01-22 18:32+0100\n"
12 | "PO-Revision-Date: 2019-02-06 14:43+0600\n"
13 | "Last-Translator: Dead Mozay \n"
14 | "Language-Team: русский \n"
15 | "Language: fr\n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20 | "X-Generator: Gtranslator 2.91.7\n"
21 |
22 | #: AudioCutter/application.py:38 AudioCutter/widgets/about.py:39
23 | #: AudioCutter/widgets/headerbar.py:43
24 | msgid "Audio Cutter"
25 | msgstr "Audio Cutter"
26 |
27 | #: AudioCutter/application.py:78
28 | msgid "Night Mode"
29 | msgstr "Ночной режим"
30 |
31 | #: AudioCutter/application.py:81
32 | msgid "Keyboard Shortcuts"
33 | msgstr "Сочетания клавиш"
34 |
35 | #: AudioCutter/application.py:84
36 | msgid "About Audio Cutter"
37 | msgstr "О программе Audio Cutter"
38 |
39 | #: AudioCutter/const.py:21
40 | msgid "OGG Audio"
41 | msgstr "Audio OGG"
42 |
43 | #: AudioCutter/const.py:22
44 | msgid "MIDI"
45 | msgstr "MIDI"
46 |
47 | #: AudioCutter/const.py:23
48 | msgid "Waveform Audio Format"
49 | msgstr "Аудио формат (WAV)"
50 |
51 | #: AudioCutter/const.py:24
52 | msgid "AAC Audio"
53 | msgstr "Audio AAC"
54 |
55 | #: AudioCutter/const.py:25
56 | msgid "MP3 Audio"
57 | msgstr "Аудио MP3"
58 |
59 | #: AudioCutter/const.py:26
60 | msgid "3GPP Audio Container"
61 | msgstr "Аудио контейнер 3GPP"
62 |
63 | #: AudioCutter/const.py:27
64 | msgid "3GPP2 Audio Container"
65 | msgstr "Аудио контейнер 3GPP2"
66 |
67 | #: AudioCutter/const.py:28
68 | msgid "WEBM Audio"
69 | msgstr "Аудио Webm"
70 |
71 | #: AudioCutter/widgets/about.py:40
72 | msgid "translator-credits"
73 | msgstr "Dead_Mozay"
74 |
75 | #: AudioCutter/widgets/about.py:42
76 | msgid "Cut your audio files like no one does"
77 | msgstr "Вырезать аудио файлы, так как никто не делает"
78 |
79 | #. Save Button
80 | #: AudioCutter/widgets/actionbar.py:52
81 | msgid "Save"
82 | msgstr "Сохранить"
83 |
84 | #. Open button
85 | #: AudioCutter/widgets/headerbar.py:57 AudioCutter/widgets/window.py:125
86 | msgid "Open"
87 | msgstr "Открыть"
88 |
89 | #: AudioCutter/widgets/headerbar.py:88
90 | msgid "Play"
91 | msgstr "Проиграть"
92 |
93 | #: AudioCutter/widgets/headerbar.py:91
94 | msgid "Pause"
95 | msgstr "Пауза"
96 |
97 | #: AudioCutter/widgets/settings.py:58
98 | msgid "Settings"
99 | msgstr "Настройки"
100 |
101 | #: AudioCutter/widgets/soundconfig.py:74
102 | msgid "Start time"
103 | msgstr "Время в начале"
104 |
105 | #: AudioCutter/widgets/soundconfig.py:75
106 | msgid "Fade in"
107 | msgstr "Постепенное возростание"
108 |
109 | #: AudioCutter/widgets/soundconfig.py:82
110 | msgid "End time"
111 | msgstr "Время в конце"
112 |
113 | #: AudioCutter/widgets/soundconfig.py:83
114 | msgid "Fade out"
115 | msgstr "Постепенное затухание"
116 |
117 | #: AudioCutter/widgets/time.py:127
118 | msgid "Hours should be less than 24"
119 | msgstr "Часы должны быть меньше 24"
120 |
121 | #: AudioCutter/widgets/time.py:129
122 | msgid "Minutes must be less than 60"
123 | msgstr "Минуты должны быть меньше 24"
124 |
125 | #: AudioCutter/widgets/time.py:131
126 | msgid "Seconds must be less than 60"
127 | msgstr "Секунды должны быть меньше 24"
128 |
129 | #: AudioCutter/widgets/time.py:133 AudioCutter/widgets/time.py:135
130 | msgid "Invalid time format, please follow hh:mm:ss"
131 | msgstr "Плохой формат времени, следуйте формату hh:mm:ss"
132 |
133 | #: AudioCutter/widgets/window.py:126
134 | msgid "Cancel"
135 | msgstr "Отмена"
136 |
137 | #: AudioCutter/widgets/window.py:143
138 | msgid "Audio Files"
139 | msgstr "Аудио файлы"
140 |
--------------------------------------------------------------------------------
/AudioCutter/modules/player.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from .log import Logger
19 | from ..utils import format_ns
20 |
21 | from gi import require_version
22 | require_version('Gst', '1.0')
23 | require_version('GstPbutils', '1.0')
24 | require_version('GES', '1.0')
25 | from gi.repository import GLib, GObject, Gst, GstPbutils, GES
26 |
27 | class Player(GObject.GObject):
28 | """GStreamer player object."""
29 |
30 | __gsignals__ = {
31 | 'playing': (GObject.SignalFlags.RUN_FIRST, None, ()),
32 | 'duration-changed': (GObject.SignalFlags.RUN_FIRST, None, ()),
33 | 'paused': (GObject.SignalFlags.RUN_FIRST, None, ())
34 | }
35 |
36 | # Player's instance
37 | instance = None
38 |
39 | def __init__(self):
40 | GObject.GObject.__init__(self)
41 | GstPbutils.pb_utils_init()
42 | self._discoverer = GstPbutils.Discoverer.new(10 * Gst.SECOND)
43 |
44 | self.is_playing = False
45 | self.filepath = None
46 | self._duration = Gst.CLOCK_TIME_NONE
47 | self.asset = None
48 |
49 | self._playbin = Gst.ElementFactory.make("playbin", "player")
50 | bus = self._playbin.get_bus()
51 | bus.add_signal_watch()
52 | bus.connect("message::error", self.__on_bus_error)
53 | bus.connect("message::eos", self.__on_bus_eos)
54 | bus.connect("message::element", self.__on_bus_element)
55 | bus.connect("message::stream-start", self._on_stream_start)
56 |
57 | @staticmethod
58 | def get_default():
59 | """Return default player instance."""
60 | if Player.instance is None:
61 | Player.instance = Player()
62 | return Player.instance
63 |
64 | def play(self, *args):
65 | """Play the current audio file."""
66 | def on_duration_changed():
67 | self.emit("duration-changed")
68 | return self.is_playing
69 | if self.is_playing:
70 | # Stop the current audio file from playing
71 | self.stop()
72 | self._playbin.set_state(Gst.State.PLAYING)
73 | self.is_playing = True
74 | self.emit("playing")
75 | GLib.timeout_add_seconds(1, on_duration_changed)
76 |
77 | def pause(self, *args):
78 | """Pause the audio player."""
79 | self._playbin.set_state(Gst.State.PAUSED)
80 | self.is_playing = False
81 | self.emit("paused")
82 |
83 | def stop(self, *args):
84 | """Stop the audio Player."""
85 | self.is_playing = False
86 | self.emit("paused")
87 | self._playbin.set_state(Gst.State.NULL)
88 |
89 | def set_open_file(self, filepath):
90 | """Set the current open file."""
91 | self.filepath = filepath
92 | self.asset = GES.UriClipAsset.request_sync(self.filepath.get_uri())
93 | self.stop()
94 | self._playbin.set_property('uri', self.filepath.get_uri())
95 |
96 | @property
97 | def duration(self):
98 | """Return the current file duration."""
99 | if self.asset:
100 | return self.asset.get_duration()
101 | return None
102 |
103 | @property
104 | def title(self):
105 | if self.asset:
106 | tags = self.asset.get_info().get_tags()
107 | if tags:
108 | (exists, title) = tags.get_string_index("title", 0)
109 | if exists and title.strip():
110 | return title
111 | return GLib.path_get_basename(self.filepath.get_path())
112 |
113 | @property
114 | def uri(self):
115 | return self.filepath.get_uri()
116 |
117 | def __on_bus_eos(self, *args):
118 | """End of stream handler."""
119 | Logger.debug("[Player] End of stream reacher")
120 | self.stop()
121 |
122 | def __on_bus_element(self, bus, message):
123 | print("element {} {}".format(bus, message))
124 |
125 | def __on_bus_error(self, bus, message):
126 | error = message.parse_error()[0].message
127 | Logger.error("[Player] Stream Error: {}".format(error))
128 | self.stop()
129 |
130 | def _on_stream_start(self, *args):
131 | Logger.debug("[Player] Stream started")
132 |
--------------------------------------------------------------------------------
/AudioCutter/widgets/soundconfig.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from gettext import gettext as _
19 |
20 | from .time import TimeButton
21 | from ..utils import format_ns
22 |
23 | from gi import require_version
24 | require_version("Gtk", "3.0")
25 | from gi.repository import Gtk
26 |
27 |
28 | class SoundConfig(Gtk.Box):
29 | """
30 | Audio configurations like:
31 | - Start time
32 | - End time
33 | - Fade in/out
34 | """
35 | instance = None
36 |
37 | def __init__(self):
38 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
39 | self.set_border_width(18)
40 | self._start_time = TimeButton()
41 | self._end_time = TimeButton()
42 | self._fade_in = Gtk.Switch()
43 | self._fade_out = Gtk.Switch()
44 | self._setup_widgets()
45 |
46 | @staticmethod
47 | def get_default():
48 | if SoundConfig.instance is None:
49 | SoundConfig.instance = SoundConfig()
50 | return SoundConfig.instance
51 |
52 | @property
53 | def start_time(self):
54 | return self._start_time
55 |
56 | @property
57 | def end_time(self):
58 | return self._end_time
59 |
60 | @property
61 | def is_fade_in(self):
62 | return self._fade_in.get_active()
63 |
64 | @property
65 | def is_fade_out(self):
66 | return self._fade_out.get_active()
67 |
68 | def _setup_widgets(self):
69 | """Setup the main SoundConfig widgets."""
70 | # Start time/ Fade in widget
71 | self._start_time.set_sensitive(False)
72 | self._fade_in.set_sensitive(False)
73 | start_box = SoundConfig.setup_box([
74 | (_("Start time"), self._start_time),
75 | (_("Fade in"), self._fade_in)
76 | ])
77 | self.pack_start(start_box, True, True, 6)
78 | # End time/ Fade out widget
79 | self._fade_out.set_sensitive(False)
80 | self._end_time.set_sensitive(False)
81 | end_box = SoundConfig.setup_box([
82 | (_("End time"), self._end_time),
83 | (_("Fade out"), self._fade_out)
84 | ])
85 | self.pack_end(end_box, True, True, 6)
86 |
87 | @staticmethod
88 | def setup_box(list_):
89 | """Setup a listbox from a list of (label, widget)."""
90 | # Setup the listbox
91 | listbox = Gtk.ListBox()
92 | listbox.get_style_context().add_class("config-list-box")
93 | listbox.set_halign(Gtk.Align.FILL)
94 | listbox.set_valign(Gtk.Align.FILL)
95 | listbox.set_selection_mode(Gtk.SelectionMode.NONE)
96 |
97 | def _resize_listbox_childs(listbox):
98 | """Set the listbox childs to have the same height."""
99 | max_height = 0
100 | for row in listbox.get_children():
101 | height = row.get_allocated_height()
102 | if height > max_height:
103 | max_height = height
104 | for row in listbox.get_children():
105 | row.props.height_request = max_height
106 |
107 | for label, widget in list_:
108 | box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
109 | spacing=6)
110 | widget.set_valign(Gtk.Align.CENTER)
111 | label_ = Gtk.Label(label=label)
112 | label_.get_style_context().add_class("config-list-box-label")
113 | box.pack_start(label_, False, False, 12)
114 | box.pack_end(widget, False, False, 12)
115 | listboxrow = Gtk.ListBoxRow()
116 | listboxrow.get_style_context().add_class("config-list-box-row")
117 | listboxrow.add(box)
118 | listbox.add(listboxrow)
119 | listbox.connect("realize", _resize_listbox_childs)
120 |
121 | return listbox
122 |
123 | def set_state(self, state):
124 | """Set the SoundConfig state active/inactive."""
125 | self._end_time.set_sensitive(state)
126 | self._start_time.set_sensitive(state)
127 | self._fade_in.set_sensitive(state)
128 | self._fade_out.set_sensitive(state)
129 |
130 | def set_duration(self, duration):
131 | """Set the max duration."""
132 | time_obj = format_ns(duration)
133 | self._start_time.duration = time_obj
134 | self._end_time.duration = time_obj
135 | self._end_time.time = time_obj
136 |
--------------------------------------------------------------------------------
/AudioCutter/widgets/time.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from gettext import gettext as _
19 |
20 | from ..objects import Time
21 |
22 | from .notification import Notification
23 |
24 | from gi import require_version
25 | require_version("Gtk", "3.0")
26 | from gi.repository import Gio, Gtk
27 |
28 |
29 | class TimeButton(Gtk.Box):
30 | """A Time widget. used for start/end time."""
31 |
32 | def __init__(self):
33 | Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
34 | self._entry = Gtk.Entry()
35 | self._up_btn = Gtk.Button()
36 | self._lower_btn = Gtk.Button()
37 | self._duration = Time(0, 0, 0)
38 | self.time = Time(0, 0, 0)
39 | self._setup_widgets()
40 |
41 | def _setup_widgets(self):
42 | """Create the Time Button widgets."""
43 | # Time entry
44 | self._entry.set_max_length(8)
45 | self._entry.set_width_chars(8)
46 | self._entry.connect("changed", self._on_type)
47 | self._entry.set_max_width_chars(8)
48 |
49 | # Up btn
50 | up_icn = Gio.ThemedIcon(name="list-add-symbolic")
51 | up_img = Gtk.Image.new_from_gicon(up_icn, Gtk.IconSize.BUTTON)
52 | self._up_btn.set_image(up_img)
53 | self._up_btn.connect("clicked", self.step_up)
54 | self._up_btn.get_style_context().add_class("flat")
55 |
56 | # Lower btn
57 | lower_icn = Gio.ThemedIcon(name="list-remove-symbolic")
58 | lower_img = Gtk.Image.new_from_gicon(lower_icn, Gtk.IconSize.BUTTON)
59 | self._lower_btn.set_image(lower_img)
60 | self._lower_btn.connect("clicked", self.step_down)
61 | self._lower_btn.get_style_context().add_class("flat")
62 |
63 | self.pack_start(self._entry, False, False, 0)
64 | self.pack_start(self._lower_btn, False, False, 0)
65 | self.pack_start(self._up_btn, False, False, 0)
66 |
67 | @property
68 | def duration(self):
69 | return self._duration
70 |
71 | @duration.setter
72 | def duration(self, duration):
73 | """
74 | Set the max duration based on the Time object.
75 | The time object (player.duration)
76 | """
77 | self._duration = duration.copy()
78 | self.__redraw()
79 |
80 | def step_down(self, *args):
81 | self.time.down()
82 | if self.time.total < 0:
83 | time = Time(0, 0, 0)
84 | else:
85 | time = self.time
86 | self.time = time
87 |
88 | def step_up(self, *args):
89 | self.time.up()
90 | if self.time.total >= self.duration.total:
91 | time = self.duration.copy()
92 | else:
93 | time = self.time
94 | self.time = time
95 |
96 | @property
97 | def time(self):
98 | return self._time
99 |
100 | @time.setter
101 | def time(self, time):
102 | self._time = time.copy()
103 | self.__redraw()
104 |
105 | def __redraw(self):
106 | if self.duration and self._time.total >= self.duration.total:
107 | self._up_btn.set_sensitive(False)
108 | else:
109 | self._up_btn.set_sensitive(True)
110 | if self._time.total <= 0:
111 | self._lower_btn.set_sensitive(False)
112 | else:
113 | self._lower_btn.set_sensitive(True)
114 | label = "{0:02d}:{1:02d}:{2:02d}".format(self._time.hours,
115 | self._time.minutes,
116 | self._time.seconds)
117 | self._entry.set_text(label)
118 |
119 | def _on_type(self, entry):
120 | song_time = entry.get_text().strip().split(":")
121 | # Make sure we have got hh:mm:ss
122 | message = None
123 | if len(song_time) == 3:
124 | try:
125 | hours, minutes, seconds = list(map(int, song_time))
126 | if hours > 24:
127 | message = _("Hours should be less than 24")
128 | elif minutes > 60:
129 | message = _("Minutes must be less than 60")
130 | elif seconds > 60:
131 | message = _("Seconds must be less than 60")
132 | except (TypeError, ValueError):
133 | message = _("Invalid time format, please follow hh:mm:ss")
134 | else:
135 | message = _("Invalid time format, please follow hh:mm:ss")
136 | if message:
137 | Notification.get_default().message = message
138 | entry.get_style_context().add_class("entry-error")
139 | else:
140 | entry.get_style_context().remove_class("entry-error")
141 |
--------------------------------------------------------------------------------
/AudioCutter/application.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | from gettext import gettext as _
19 |
20 | from .widgets import Window, AboutDialog, ShortcutsWindow, SettingsWindow
21 | from .modules import Logger, Settings
22 |
23 | from gi import require_version
24 | require_version("Gtk", "3.0")
25 | from gi.repository import Gdk, Gio, Gtk, GLib
26 |
27 |
28 | class Application(Gtk.Application):
29 | """Main Application Object."""
30 | # Main app instance
31 | instance = None
32 |
33 | def __init__(self):
34 | Gtk.Application.__init__(self,
35 | application_id="com.github.bilelmoussaoui.AudioCutter",
36 | flags=Gio.ApplicationFlags.FLAGS_NONE)
37 | GLib.set_application_name(_("Audio Cutter"))
38 | GLib.set_prgname("Audio Cutter")
39 | self.app_menu = Gio.Menu()
40 | self._setup_css()
41 | Application.instance = self
42 |
43 | @staticmethod
44 | def get_default():
45 | """Returns the default Application instance."""
46 | return Application.instance
47 |
48 | def do_startup(self):
49 | """startup signal handler."""
50 | Gtk.Application.do_startup(self)
51 | self._setup_app_menu()
52 |
53 | def do_activate(self):
54 | """activate signal handler."""
55 | window = Window.get_default()
56 | window.set_application(self)
57 | self.add_window(window)
58 | window.show_all()
59 | window.present()
60 |
61 | def _setup_css(self):
62 | """Setup the CSS."""
63 | resource = 'resource:////com/github/bilelmoussaoui/AudioCutter/style.css'
64 | css_file = Gio.File.new_for_uri(resource)
65 | cssProvider = Gtk.CssProvider()
66 | screen = Gdk.Screen.get_default()
67 | styleContext = Gtk.StyleContext()
68 | cssProvider.load_from_file(css_file)
69 | styleContext.add_provider_for_screen(screen, cssProvider,
70 | Gtk.STYLE_PROVIDER_PRIORITY_USER)
71 | Logger.debug("Loading css file {}".format(css_file))
72 |
73 | def _setup_app_menu(self):
74 | """Create the appmenu."""
75 | # Help section
76 | help_content = Gio.Menu.new()
77 | help_content.append_item(Gio.MenuItem.new(_("Night Mode"),
78 | "app.night_mode"))
79 | if Gtk.get_major_version() >= 3 and Gtk.get_minor_version() >= 20:
80 | help_content.append_item(Gio.MenuItem.new(_("Keyboard Shortcuts"),
81 | "app.shortcuts"))
82 |
83 | help_content.append_item(Gio.MenuItem.new(_("About Audio Cutter"), "app.about"))
84 | help_section = Gio.MenuItem.new_section(None, help_content)
85 | self.app_menu.append_item(help_section)
86 |
87 | # Night Mode action
88 | is_night_mode = Settings.get_default().is_night_mode
89 | is_night_mode = GLib.Variant.new_boolean(is_night_mode)
90 | action = Gio.SimpleAction.new_stateful("night_mode", None,
91 | is_night_mode)
92 | action.connect("change-state", self._on_night_mode)
93 | self.add_action(action)
94 |
95 | # Shortcuts action
96 | if Gtk.get_major_version() >= 3 and Gtk.get_minor_version() >= 20:
97 | action = Gio.SimpleAction.new("shortcuts", None)
98 | action.connect("activate", self._on_shortcuts)
99 | self.add_action(action)
100 |
101 | # About action
102 | action = Gio.SimpleAction.new("about", None)
103 | action.connect("activate", self._on_about)
104 | self.add_action(action)
105 |
106 | # Quit action
107 | action = Gio.SimpleAction.new("quit", None)
108 | action.connect("activate", self._on_quit)
109 | self.add_action(action)
110 |
111 | def _on_night_mode(self, action, *args):
112 | """Switch the night mode on/off."""
113 | settings = Settings.get_default()
114 | is_night_mode = not settings.is_night_mode
115 | action.set_state(GLib.Variant.new_boolean(is_night_mode))
116 | settings.is_night_mode = is_night_mode
117 | Gtk.Settings.get_default().set_property(
118 | "gtk-application-prefer-dark-theme", is_night_mode)
119 |
120 | def _on_shortcuts(self, *args):
121 | """Shows keyboard shortcuts."""
122 | shortcuts = ShortcutsWindow()
123 | shortcuts.set_transient_for(Window.get_default())
124 | shortcuts.show()
125 |
126 | def _on_about(self, *args):
127 | """Shows about dialog."""
128 | dialog = AboutDialog()
129 | dialog.set_transient_for(Window.get_default())
130 | dialog.run()
131 | dialog.destroy()
132 |
133 | def _on_quit(self, *args):
134 | """Quit the application."""
135 | window = Window.get_default()
136 | window.destroy()
137 | self.quit()
138 |
--------------------------------------------------------------------------------
/AudioCutter/widgets/window.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 |
19 | from os import path
20 | from gettext import gettext as _
21 | from .actionbar import ActionBar
22 | from .headerbar import HeaderBar
23 | from .soundconfig import SoundConfig
24 | from .zoombox import ZoomBox
25 | from .notification import Notification
26 | from .audio_graph import AudioGraph
27 | from ..modules import Logger, Player, Settings, Exporter
28 | from ..const import AUDIO_MIMES
29 | from .loading import Loading
30 |
31 | from gi import require_version
32 | require_version("Gtk", "3.0")
33 | from gi.repository import Gtk, Gio
34 |
35 |
36 | class Window(Gtk.ApplicationWindow):
37 | """Main Window Object."""
38 | instance = None
39 |
40 | def __init__(self):
41 | Gtk.Window.__init__(self)
42 | self.connect("delete-event", lambda x, y: self._on_close())
43 | self._main = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
44 | self._setup_window()
45 | self._setup_widgets()
46 | self._restore_state()
47 | Player.get_default().connect("playing", self._on_play)
48 | Player.get_default().connect("paused", self._on_paused)
49 | Player.get_default().connect("duration-changed", self._on_duration_changed)
50 |
51 | @staticmethod
52 | def get_default():
53 | """Return the dafault instance of Window."""
54 | if Window.instance is None:
55 | Window.instance = Window()
56 | return Window.instance
57 |
58 | def _setup_window(self):
59 | self.set_resizable(False)
60 | self.set_default_size(600, 300)
61 |
62 | def _setup_widgets(self):
63 | """Setup the main widgets."""
64 | # Main widget
65 | self.add(self._main)
66 | # Headerbar
67 | headerbar = HeaderBar.get_default()
68 | headerbar.connect("open-file", self._open_file)
69 | headerbar.play_btn.connect("clicked", self._play)
70 | # Set up the app menu in other DE than GNOME
71 | from ..application import Application
72 | app_menu = Application.get_default().app_menu
73 | menu_btn = headerbar.menu_btn
74 | popover = Gtk.Popover.new_from_model(menu_btn, app_menu)
75 | menu_btn.connect("clicked", self._toggle_popover, popover)
76 | self.set_titlebar(headerbar)
77 |
78 | # Action Bar
79 | actionbar = ActionBar.get_default()
80 | actionbar.connect("selected-format", self._on_export)
81 | self._main.pack_end(actionbar, False, False, 0)
82 |
83 | # Notification
84 | notification = Notification.get_default()
85 | self._main.pack_start(notification, False, False, 0)
86 |
87 | # Audio Graph
88 | self.main_stack = Gtk.Stack()
89 |
90 | self.audio_graph_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
91 | self.audio_graph_box.get_style_context().add_class("audio-graph-container")
92 | self.zoombox = ZoomBox()
93 |
94 | overlay = Gtk.Overlay()
95 | overlay.add(self.audio_graph_box)
96 | overlay.add_overlay(self.zoombox)
97 | self.main_stack.add_named(overlay, "wave")
98 |
99 | loading = Loading()
100 | self.main_stack.add_named(loading, "loading")
101 | self._main.pack_start(self.main_stack, True, True, 0)
102 |
103 | # Config Box
104 | sound_config = SoundConfig.get_default()
105 | self._main.pack_end(sound_config, False, False, 0)
106 |
107 | def _on_play(self, player):
108 | HeaderBar.get_default().set_is_playing(True)
109 |
110 | def _on_paused(self, player):
111 | HeaderBar.get_default().set_is_playing(False)
112 |
113 | def _play(self, *args):
114 | player = Player.get_default()
115 | if player.is_playing:
116 | player.pause()
117 | else:
118 | player.play()
119 |
120 | def _open_file(self, *args):
121 | """Open an open file dialog to select an audio file."""
122 | file_chooser = Gtk.FileChooserNative()
123 | file_chooser.set_action(Gtk.FileChooserAction.OPEN)
124 | file_chooser.set_transient_for(self)
125 | file_chooser.set_accept_label(_("Open"))
126 | file_chooser.set_cancel_label(_("Cancel"))
127 | Window._add_filters(file_chooser)
128 | response = file_chooser.run()
129 | uri = None
130 | if response == Gtk.ResponseType.ACCEPT:
131 | uri = file_chooser.get_uri()
132 | file_chooser.destroy()
133 | if uri:
134 | self._set_open_file(Gio.File.new_for_uri(uri))
135 | Logger.debug("File Selected {}".format(uri))
136 | else:
137 | Logger.debug("Open file dialog closed without selecting a file.")
138 |
139 | @staticmethod
140 | def _add_filters(dialog):
141 | """Add audio filters to the open file dialog."""
142 | filters = Gtk.FileFilter()
143 | filters.set_name(_("Audio Files"))
144 | for mimetype in AUDIO_MIMES:
145 | filters.add_mime_type(mimetype)
146 | dialog.add_filter(filters)
147 |
148 | def _set_open_file(self, f):
149 | """Set a filename as opened."""
150 | player = Player.get_default()
151 | soundconfig = SoundConfig.get_default()
152 | self.main_stack.set_visible_child_name("loading")
153 | loading = self.main_stack.get_child_by_name("loading")
154 | loading.start()
155 | player.set_open_file(f)
156 | HeaderBar.get_default().set_audio_title(player.title)
157 | ActionBar.get_default().set_state(True)
158 | soundconfig.set_state(True)
159 | soundconfig.set_duration(player.duration)
160 | Settings.get_default().last_file = f.get_uri()
161 |
162 | audio_graph = AudioGraph(f.get_uri(), player.asset)
163 | for child in self.audio_graph_box.get_children():
164 | self.audio_graph_box.remove(child)
165 | self.audio_graph_box.pack_start(audio_graph, True, True, 0)
166 | audio_graph.connect("draw-done", self._on_waveform_ready)
167 | self.zoombox.zoom_up.connect("clicked", audio_graph.zoomIn)
168 | self.zoombox.zoom_down.connect("clicked", audio_graph.zoomOut)
169 |
170 | def _on_waveform_ready(self, *args):
171 | loading = self.main_stack.get_child_by_name("loading")
172 | loading.stop()
173 | self.main_stack.set_visible_child_name("wave")
174 |
175 | def _on_duration_changed(self, *args):
176 | sound_config = SoundConfig.get_default()
177 | sound_config.start_time.step_up()
178 |
179 | def _on_export(self, action_bar, audio_format):
180 | sound_config = SoundConfig.get_default()
181 | is_fade_in = sound_config.is_fade_in
182 | is_fade_out = sound_config.is_fade_out
183 | start_time = sound_config.start_time.time.total
184 | end_time = sound_config.end_time.time.total
185 | audio_path = Player.get_default().uri
186 | exporter = Exporter(start_time=start_time, end_time=end_time,
187 | is_fade_in=is_fade_in, is_fade_out=is_fade_out,
188 | path=audio_path, audio_format=audio_format)
189 | exporter.do()
190 |
191 |
192 | def _toggle_popover(self, button, popover):
193 | """Toggle the app menu popover."""
194 | if popover:
195 | if popover.get_visible():
196 | popover.hide()
197 | else:
198 | popover.show_all()
199 |
200 | def _restore_state(self):
201 | """Restore the latest position and opened file."""
202 | settings = Settings.get_default()
203 | pos_x, pos_y = settings.window_position
204 | if pos_x != 0 and pos_y != 0:
205 | self.move(pos_x, pos_y)
206 | else:
207 | self.set_position(Gtk.WindowPosition.CENTER)
208 |
209 | last_file = Gio.File.new_for_uri(settings.last_file)
210 | if last_file and last_file.query_exists():
211 | self._set_open_file(last_file)
212 |
213 | def _on_close(self):
214 | """Window delete event handler."""
215 | # TODO: ask the user if he wants to save the current modification?
216 | # Save the latest window position
217 | Settings.get_default().window_position = self.get_position()
218 |
--------------------------------------------------------------------------------
/AudioCutter/widgets/audio_graph.py:
--------------------------------------------------------------------------------
1 | """
2 | Your favorite Audio Cutter.
3 | Author : Bilal Elmoussaoui (bil.elmoussaoui@gmail.com)
4 | Artist : Alfredo Hernández
5 | Website : https://github.com/bil-elmoussaoui/Audio-Cutter
6 | This file is part of AudioCutter.
7 | AudioCutter is free software: you can redistribute it and/or
8 | modify it under the terms of the GNU General Public License as published
9 | by the Free Software Foundation, either version 3 of the License, or
10 | (at your option) any later version.
11 | AudioCutter is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 | You should have received a copy of the GNU General Public License
16 | along with AudioCutter. If not, see .
17 | """
18 | import numpy
19 | import cairo
20 | from os import path
21 | from gi import require_version
22 | require_version("Gtk", "3.0")
23 | require_version("GES", "1.0")
24 | from gi.repository import Gst, Gtk, GES, GObject, GLib, Gdk
25 |
26 | from ..modules import Player
27 | from ..utils import get_wavefile_location_for_uri
28 | import renderer
29 |
30 |
31 | SAMPLE_DURATION = Gst.SECOND / 100
32 | MARGIN = 500
33 |
34 |
35 | class Zoomable(object):
36 | """Base class for conversions between timeline timestamps and UI pixels.
37 | Complex Timeline interfaces v2 (01 Jul 2008)
38 | Zoomable
39 | -----------------------
40 | Interface for the Complex Timeline widgets for setting, getting,
41 | distributing and modifying the zoom ratio and the size of the widget.
42 | A zoomratio is the number of pixels per second
43 | ex : 10.0 = 10 pixels for a second
44 | ex : 0.1 = 1 pixel for 10 seconds
45 | ex : 1.0 = 1 pixel for a second
46 | Class Methods
47 | . pixelToNs(pixels)
48 | . nsToPixels(time)
49 | . setZoomRatio
50 | Instance Methods
51 | . zoomChanged()
52 | """
53 |
54 | sigid = None
55 | _instances = []
56 | max_zoom = 1000.0
57 | min_zoom = 0.25
58 | zoom_steps = 100
59 | zoom_range = max_zoom - min_zoom
60 | _cur_zoom = 20
61 | zoomratio = None
62 |
63 |
64 | def __init__(self):
65 | # FIXME: ideally we should deprecate this
66 | Zoomable.addInstance(self)
67 | if Zoomable.zoomratio is None:
68 | Zoomable.zoomratio = self.computeZoomRatio(self._cur_zoom)
69 |
70 | def __del__(self):
71 | if self in Zoomable._instances:
72 | # FIXME: ideally we should deprecate this and spit a warning here
73 | self._instances.remove(self)
74 |
75 | @classmethod
76 | def addInstance(cls, instance):
77 | cls._instances.append(instance)
78 |
79 | @classmethod
80 | def removeInstance(cls, instance):
81 | cls._instances.remove(instance)
82 |
83 | @classmethod
84 | def setZoomRatio(cls, ratio):
85 | ratio = min(max(cls.min_zoom, ratio), cls.max_zoom)
86 | if cls.zoomratio != ratio:
87 | cls.zoomratio = ratio
88 | for inst in cls._instances:
89 | inst.zoomChanged()
90 |
91 | @classmethod
92 | def setZoomLevel(cls, level):
93 | level = int(max(0, min(level, cls.zoom_steps)))
94 | if level != cls._cur_zoom:
95 | cls._cur_zoom = level
96 | cls.setZoomRatio(cls.computeZoomRatio(level))
97 |
98 | @classmethod
99 | def getCurrentZoomLevel(cls):
100 | return cls._cur_zoom
101 |
102 | @classmethod
103 | def zoomIn(cls, *args):
104 | cls.setZoomLevel(cls._cur_zoom + 1)
105 |
106 | @classmethod
107 | def zoomOut(cls, *args):
108 | cls.setZoomLevel(cls._cur_zoom - 1)
109 |
110 | @classmethod
111 | def computeZoomRatio(cls, x):
112 | return ((((float(x) / cls.zoom_steps) ** 3) * cls.zoom_range) +
113 | cls.min_zoom)
114 |
115 | @classmethod
116 | def computeZoomLevel(cls, ratio):
117 | return int((
118 | (max(0, ratio - cls.min_zoom) /
119 | cls.zoom_range) ** (1.0 / 3.0)) * cls.zoom_steps)
120 |
121 | @classmethod
122 | def pixelToNs(cls, pixel):
123 | """Returns the duration equivalent of the specified pixel."""
124 | return int(pixel * Gst.SECOND / cls.zoomratio)
125 |
126 | @classmethod
127 | def pixelToNsAt(cls, pixel, ratio):
128 | """Returns the duration equivalent of the specified pixel."""
129 | return int(pixel * Gst.SECOND / ratio)
130 |
131 | @classmethod
132 | def nsToPixel(cls, duration):
133 | """Returns the pixel equivalent of the specified duration"""
134 | # Here, a long time ago (206f3a05), a pissed programmer said:
135 | # DIE YOU CUNTMUNCH CLOCK_TIME_NONE UBER STUPIDITY OF CRACK BINDINGS !!
136 | if duration == Gst.CLOCK_TIME_NONE:
137 | return 0
138 | return int((float(duration) / Gst.SECOND) * cls.zoomratio)
139 |
140 | @classmethod
141 | def nsToPixelAccurate(cls, duration):
142 | """Returns the pixel equivalent of the specified duration."""
143 | # Here, a long time ago (206f3a05), a pissed programmer said:
144 | # DIE YOU CUNTMUNCH CLOCK_TIME_NONE UBER STUPIDITY OF CRACK BINDINGS !!
145 | if duration == Gst.CLOCK_TIME_NONE:
146 | return 0
147 | return ((float(duration) / Gst.SECOND) * cls.zoomratio)
148 |
149 | def zoomChanged(self):
150 | pass
151 |
152 | class PreviewerBin(Gst.Bin):
153 | """Baseclass for elements gathering datas to create previews."""
154 |
155 | def __init__(self, bin_desc):
156 | Gst.Bin.__init__(self)
157 |
158 | self.internal_bin = Gst.parse_bin_from_description(bin_desc, True)
159 | self.add(self.internal_bin)
160 | self.add_pad(Gst.GhostPad.new(None, self.internal_bin.sinkpads[0]))
161 | self.add_pad(Gst.GhostPad.new(None, self.internal_bin.srcpads[0]))
162 |
163 | def finalize(self, proxy=None):
164 | """Finalizes the previewer, saving data to the disk if needed."""
165 | pass
166 |
167 |
168 | class WaveformPreviewer(PreviewerBin):
169 | """Bin to generate and save waveforms as a .npy file."""
170 |
171 | __gproperties__ = {
172 | "uri": (str,
173 | "uri of the media file",
174 | "A URI",
175 | "",
176 | GObject.ParamFlags.READWRITE),
177 | "duration": (GObject.TYPE_UINT64,
178 | "Duration",
179 | "Duration",
180 | 0, GLib.MAXUINT64 - 1, 0, GObject.ParamFlags.READWRITE)
181 | }
182 |
183 | def __init__(self):
184 | PreviewerBin.__init__(self,
185 | "audioconvert ! audioresample ! "
186 | "audio/x-raw,channels=1 ! level name=level"
187 | " ! audioconvert ! audioresample")
188 | self.level = self.internal_bin.get_by_name("level")
189 | self.peaks = None
190 |
191 | self.uri = None
192 | self.wavefile = None
193 | self.passthrough = False
194 | self.samples = []
195 | self.n_samples = 0
196 | self.duration = 0
197 | self.prev_pos = 0
198 |
199 | def do_get_property(self, prop):
200 |
201 | if prop.name == 'uri':
202 | return self.uri
203 | elif prop.name == 'duration':
204 | return self.duration
205 | else:
206 | raise AttributeError('unknown property %s' % prop.name)
207 |
208 | def do_set_property(self, prop, value):
209 | if prop.name == 'uri':
210 | self.uri = value
211 | self.wavefile = get_wavefile_location_for_uri(self.uri)
212 | self.passthrough = path.exists(self.wavefile)
213 | elif prop.name == 'duration':
214 | self.duration = value
215 | self.n_samples = self.duration / SAMPLE_DURATION
216 | else:
217 | raise AttributeError('unknown property %s' % prop.name)
218 |
219 | # pylint: disable=arguments-differ
220 | def do_post_message(self, message):
221 | if not self.passthrough and \
222 | message.type == Gst.MessageType.ELEMENT and \
223 | message.src == self.level:
224 | struct = message.get_structure()
225 | peaks = None
226 | if struct:
227 | peaks = struct.get_value("rms")
228 |
229 | if peaks:
230 | stream_time = struct.get_value("stream-time")
231 |
232 | if self.peaks is None:
233 | self.peaks = []
234 | for unused_channel in peaks:
235 | self.peaks.append([0] * int(self.n_samples))
236 |
237 | pos = int(stream_time / SAMPLE_DURATION)
238 | if pos >= len(self.peaks[0]):
239 | return
240 |
241 | for i, val in enumerate(peaks):
242 | if val < 0:
243 | val = 10 ** (val / 20) * 100
244 | else:
245 | val = self.peaks[i][pos - 1]
246 |
247 | # Linearly joins values between to known samples values.
248 | unknowns = range(self.prev_pos + 1, pos)
249 | if unknowns:
250 | prev_val = self.peaks[i][self.prev_pos]
251 | linear_const = (val - prev_val) / len(unknowns)
252 | for temppos in unknowns:
253 | self.peaks[i][temppos] = self.peaks[i][temppos -
254 | 1] + linear_const
255 |
256 | self.peaks[i][pos] = val
257 |
258 | self.prev_pos = pos
259 |
260 | return Gst.Bin.do_post_message(self, message)
261 |
262 | def finalize(self):
263 | """Finalizes the previewer, saving data to file if needed."""
264 | if not self.passthrough and self.peaks:
265 | # Let's go mono.
266 | if len(self.peaks) > 1:
267 | samples = (numpy.array(
268 | self.peaks[0]) + numpy.array(self.peaks[1])) / 2
269 | else:
270 | samples = numpy.array(self.peaks[0])
271 |
272 | self.samples = list(samples)
273 | with open(self.wavefile, 'wb') as wavefile:
274 | numpy.save(wavefile, samples)
275 |
276 |
277 | Gst.Element.register(None, "waveformbin", Gst.Rank.NONE, WaveformPreviewer)
278 |
279 |
280 | class AudioGraph(Gtk.Layout, Zoomable):
281 | """The graph of the audio."""
282 |
283 | __gsignals__ = {
284 | 'draw-done': (GObject.SignalFlags.RUN_FIRST, None, ()),
285 | }
286 |
287 | def __init__(self, uri, asset):
288 | GObject.GObject.__init__(self)
289 | Gtk.Layout.__init__(self)
290 | Zoomable.__init__(self)
291 |
292 | self._asset = asset
293 | self._uri = uri
294 | self.wavefile = None
295 | self.pipeline = None
296 | self._wavebin = None
297 | self.n_samples = asset.get_duration() / SAMPLE_DURATION
298 | self.samples = None
299 | self._force_redraw = True
300 | self.peaks = None
301 | self.discovered = False
302 | self._start = 0
303 | self._end = 0
304 | self._surface_x = 0
305 | self._start_levels_discovery()
306 | self.connect("notify::height-request", self._height_changed_cb)
307 | self.show_all()
308 |
309 |
310 | def emit(self, *args):
311 | GLib.idle_add(GObject.GObject.emit, self, *args)
312 |
313 | def _launch_pipeline(self):
314 | self.pipeline = Gst.parse_launch("uridecodebin name=decode uri=" +
315 | self._uri + " ! waveformbin name=wave"
316 | " ! fakesink qos=false name=faked")
317 |
318 | Gst.ElementFactory.make("uritranscodebin", None)
319 |
320 | clock = GObject.new(GObject.type_from_name("GstCpuThrottlingClock"))
321 | clock.props.cpu_usage = 90
322 | self.pipeline.use_clock(clock)
323 |
324 | faked = self.pipeline.get_by_name("faked")
325 | faked.props.sync = True
326 | self._wavebin = self.pipeline.get_by_name("wave")
327 |
328 | self._wavebin.props.uri = self._asset.get_id()
329 | self._wavebin.props.duration = self._asset.get_duration()
330 | decode = self.pipeline.get_by_name("decode")
331 | decode.connect("autoplug-select", self._autoplug_select_cb)
332 | bus = self.pipeline.get_bus()
333 | self.pipeline.set_state(Gst.State.PLAYING)
334 | bus.add_signal_watch()
335 | self.n_samples = self._asset.get_duration() / SAMPLE_DURATION
336 | bus.connect("message::error", self.__on_bus_error)
337 | bus.connect("message::eos", self.__on_bus_eos)
338 |
339 | def __disconnect_bus(self):
340 | bus = self.pipeline.get_bus()
341 | if bus:
342 | bus.disconnect_by_func(self.__on_bus_eos)
343 | bus.disconnect_by_func(self.__on_bus_error)
344 |
345 | def __on_bus_eos(self, bus, message):
346 | """On End of stream signal."""
347 | self._prepare_samples()
348 | self._start_rendering()
349 | self.stop_generation()
350 |
351 | def __on_bus_error(self, bus, message):
352 | """On error signal."""
353 | self.stop_generation()
354 | self._num_failures += 1
355 | if self._num_failures < 2:
356 | self.__disconnect_bus()
357 | self._launch_pipeline()
358 | elif self.pipeline:
359 | Gst.debug_bin_to_dot_file_with_ts(self.pipeline,
360 | Gst.DebugGraphDetails.ALL,
361 | "error-generating-waveforms")
362 |
363 | def _height_changed_cb(self, *args):
364 | self._force_redraw = True
365 | self.queue_draw()
366 |
367 | def _autoplug_select_cb(self, unused_decode, unused_pad, unused_caps, factory):
368 | # Don't plug video decoders / parsers.
369 | if "Video" in factory.get_klass():
370 | return True
371 | return False
372 |
373 | def _prepare_samples(self):
374 | self._wavebin.finalize()
375 | self.samples = self._wavebin.samples
376 |
377 | def _start_rendering(self):
378 | self.n_samples = len(self.samples)
379 | self.discovered = True
380 | self.queue_draw()
381 | self.emit("draw-done")
382 |
383 | def start_generation(self):
384 | self._start_levels_discovery()
385 | if not self.pipeline:
386 | # No need to generate as we loaded pre-generated .wave file.
387 | return
388 | self.pipeline.set_state(Gst.State.PLAYING)
389 |
390 | def stop_generation(self):
391 | if self.pipeline:
392 | self.pipeline.set_state(Gst.State.NULL)
393 | self.__disconnect_bus()
394 | self.pipeline = None
395 |
396 | def zoomChanged(self):
397 | self._force_redraw = True
398 | self.queue_draw()
399 |
400 | def do_draw(self, context):
401 | if not self.discovered:
402 | return
403 |
404 | clipped_rect = Gdk.cairo_get_clip_rectangle(context)[1]
405 |
406 | num_inpoint_samples = self._get_num_inpoint_samples()
407 | drawn_start = self.pixelToNs(clipped_rect.x)
408 | drawn_duration = self.pixelToNs(clipped_rect.width)
409 | start = int(drawn_start / SAMPLE_DURATION) + num_inpoint_samples
410 | end = int((drawn_start + drawn_duration) /
411 | SAMPLE_DURATION) + num_inpoint_samples
412 |
413 | if self._force_redraw or self._surface_x > clipped_rect.x or self._end < end:
414 | self._start = start
415 | end = int(min(self.n_samples, end + (self.pixelToNs(MARGIN) /
416 | SAMPLE_DURATION)))
417 | self._end = end
418 | self._surface_x = clipped_rect.x
419 | surface_width = min(self.get_parent().get_allocation().width - clipped_rect.x,
420 | clipped_rect.width + MARGIN)
421 | surface_height = int(self.get_parent().get_allocation().height)
422 | self.surface = renderer.fill_surface(self.samples[start:end],
423 | surface_width,
424 | surface_height)
425 |
426 | self._force_redraw = False
427 |
428 | context.set_operator(cairo.OPERATOR_OVER)
429 | context.set_source_surface(self.surface, self._surface_x, 0)
430 | context.paint()
431 |
432 | def _get_num_inpoint_samples(self):
433 | asset_duration = self._asset.get_duration()
434 | # TODO: replace 1 with in-points
435 | return int(self.n_samples / (float(asset_duration)) / 1)
436 |
437 | def _start_levels_discovery(self, *args):
438 | # Get the wavefile location
439 | filename = get_wavefile_location_for_uri(self._uri)
440 | if path.exists(filename):
441 | # If the wavefile exists, use it to draw the waveform
442 | with open(filename, "rb") as samples:
443 | self.samples = list(numpy.load(samples))
444 | self._start_rendering()
445 | else:
446 | # Otherwise launch the pipeline
447 | self.wavefile = filename
448 | self._launch_pipeline()
449 |
--------------------------------------------------------------------------------
/data/icons/hicolor/scalable/apps/com.github.bilelmoussaoui.AudioCutter.svg:
--------------------------------------------------------------------------------
1 |
277 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 | {one line to give the program's name and a brief idea of what it does.}
635 | Copyright (C) {year} {name of author}
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | {project} Copyright (C) {year} {fullname}
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------