M .gitignore => .gitignore +1 -0
@@ 1,1 1,2 @@
**/__pycache__
+build/
M README.md => README.md +1 -0
@@ 23,6 23,7 @@ Of course, you are welcome to send issues and pull requests via email as well:
## 🧩 Requirements
- `python3`
- `py3-beautifulsoup4`
+- `py3-lxml`
- `py3-requests`
- `py3-sqlite3`
- `gtk4.0`
A bin/melon.in => bin/melon.in +96 -0
@@ 0,0 1,96 @@
+#!@PYTHON@
+
+# @prettyname@ -- @description@
+#
+# Copyright (C) 2019-2024 @authorfullname@ <@authoremail@>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import gettext
+import locale
+import os
+import sys
+
+sys.path.insert(1, '@pythondir@')
+
+builddir = os.environ.get('MESON_BUILD_ROOT')
+if builddir:
+ sys.dont_write_bytecode = True
+ sys.path.insert(1, os.environ['MESON_SOURCE_ROOT'])
+ xdg_data_dir = os.path.join(builddir, '@prefix@', '@datadir@')
+ os.putenv('XDG_DATA_DIRS', '%s:%s' % (xdg_data_dir, os.getenv('XDG_DATA_DIRS', '/usr/local/share/:/usr/share/')))
+
+
+def install_excepthook():
+ """ Make sure we exit when an unhandled exception occurs. """
+ old_hook = sys.excepthook
+
+ def new_hook(etype, evalue, etb):
+ print('Error: An unhandled exception occurs')
+
+ old_hook(etype, evalue, etb)
+
+ context = GLib.main_context_default()
+ done = False
+ while context.iteration():
+ if done:
+ continue
+ app = Gio.Application.get_default()
+ if app.window:
+ app.window.quit(force=True)
+ else:
+ app.quit()
+ done = True
+
+ sys.exit()
+
+ sys.excepthook = new_hook
+
+
+if __name__ == '__main__':
+ import gi
+
+ gi.require_version('Gtk', '4.0')
+ gi.require_version('Adw', '1')
+
+ from gi.repository import Gio
+ from gi.repository import GLib
+ from gi.repository import Gtk
+
+ install_excepthook()
+
+ # Why both locale and gettext are needed?
+ # gettext works for the python part but not for XML UI files!
+ try:
+ locale.textdomain('@projectname@')
+ locale.bindtextdomain('@projectname@', '@localedir@')
+ except AttributeError as e:
+ # Python built without gettext support doesn't have bindtextdomain() and textdomain()
+ print('Could not bind the gettext translation domain. Some translations will not work.')
+ print('Error: {}'.format(e))
+ gettext.textdomain('@projectname@')
+ gettext.bindtextdomain('@projectname@', '@localedir@')
+
+ from @projectname@.application import Application
+
+ Application.application_id = '@appid@'
+ Application.version = '@VERSION@'
+ app = Application()
+
+ try:
+ status = app.run(sys.argv)
+ except SystemExit as e:
+ status = e.code
+
+ sys.exit(status)
A bin/meson.build => bin/meson.build +30 -0
@@ 0,0 1,30 @@
+# Profiles
+conf = configuration_data()
+
+conf.set('VERSION', meson.project_version())
+conf.set('PYTHON', py_installation.full_path())
+conf.set('prefix', prefix)
+conf.set('datadir', datadir)
+conf.set('pkgdatadir', pkgdatadir)
+conf.set('pythondir', join_paths(prefix, pythondir))
+conf.set('localedir', join_paths(prefix, localedir))
+conf.set('projectname', meson.project_name())
+conf.set('prettyname', prettyname)
+conf.set('description', description)
+conf.set('authorfullname', authorfullname)
+conf.set('authoremail', authoremail)
+conf.set('appid', app_id)
+
+# Install launch script and add `run` target
+configure_file(
+ input: meson.project_name() + '.in',
+ output: meson.project_name(),
+ configuration: conf,
+ install: true,
+ install_dir: get_option('bindir')
+)
+
+script_path = join_paths(meson.project_build_root(), 'bin', meson.project_name())
+run_target('run',
+ command: [script_path]
+)
A data/icu.ccw.Melon.desktop.in => data/icu.ccw.Melon.desktop.in +15 -0
@@ 0,0 1,15 @@
+[Desktop Entry]
+Name=@prettyname@
+Comment=@description@
+Exec=@bindir@/@projectname@ %U
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=@appid@
+Terminal=false
+Type=Application
+StartupNotify=true
+Categories=Graphics;Network;Viewer;GTK;GNOME;
+# Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=viewer;videos;stream;browse;
+X-GNOME-UsesNotifications=true
+# Translators: Do NOT translate or transliterate this text (these are enum types)!
+X-Purism-FormFactor=Workstation;Mobile;
R melon.svg => data/icu.ccw.Melon.svg +0 -0
A data/meson.build => data/meson.build +33 -0
@@ 0,0 1,33 @@
+scalable_dir = join_paths(datadir, 'icons/hicolor/scalable/apps')
+install_data (
+ '@0@.svg'.format(app_id),
+ install_dir: scalable_dir,
+)
+
+gnome = import('gnome')
+
+#
+# .desktop file
+#
+desktop_conf = configuration_data()
+desktop_conf.set('bindir', join_paths(prefix, bindir))
+desktop_conf.set('prettyname', prettyname)
+# .desktop comment now hardcoded for better i18n support
+desktop_conf.set('description', description)
+desktop_conf.set('appid', app_id)
+desktop_conf.set('projectname', meson.project_name())
+
+desktop_file = configure_file(
+ input: base_id + '.desktop.in',
+ output: app_id + '.desktop',
+ configuration: desktop_conf,
+ install: true,
+ install_dir: join_paths(datadir, 'applications')
+)
+
+#
+# Dependencies
+#
+dependency('glib-2.0')
+dependency('gtk4', version: '>=4.12.1')
+dependency('libadwaita-1', version: '>=1.4.0')
M main.py => main.py +9 -26
@@ 1,29 1,12 @@
+# manual quick-run script
import sys
-import gi
-gi.require_version('Gtk', '4.0')
-gi.require_version('Adw', '1')
-from gi.repository import Gtk, Adw, Gio, GLib
+from melon.application import Application
+Application.application_id = "icu.ccw.melon"
+app = Application()
-from melon.window import MainWindow
-from melon.models import init_db
-from melon.servers.utils import get_server_instance, load_server, get_servers_list
+try:
+ status = app.run(sys.argv)
+except SystemExit as e:
+ status = e.code
-class MyApp(Adw.Application):
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- # set name
- GLib.set_application_name("Melon")
- # initialize db
- init_db()
- # this has to wait till the db is initialized
- for _,server_data in get_servers_list().items():
- instance = get_server_instance(server_data)
- load_server(instance)
- self.connect('activate', self.on_activate)
-
- def on_activate(self, app):
- self.win = MainWindow(application=app)
- self.win.present()
-
-app = MyApp(application_id="icu.ccw.melon")
-app.run(sys.argv)
+sys.exit(status)
A melon/application.py => melon/application.py +25 -0
@@ 0,0 1,25 @@
+import gi
+gi.require_version('Gtk', '4.0')
+gi.require_version('Adw', '1')
+from gi.repository import Gtk, Adw, Gio, GLib
+
+from melon.window import MainWindow
+from melon.models import init_db
+from melon.servers.utils import get_server_instance, load_server, get_servers_list
+
+class Application(Adw.Application):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ # set name
+ GLib.set_application_name("Melon")
+ # initialize db
+ init_db()
+ # this has to wait till the db is initialized
+ for _,server_data in get_servers_list().items():
+ instance = get_server_instance(server_data)
+ load_server(instance)
+ self.connect('activate', self.on_activate)
+
+ def on_activate(self, app):
+ self.win = MainWindow(application=app)
+ self.win.present()
A meson.build => meson.build +48 -0
@@ 0,0 1,48 @@
+project(
+ 'melon',
+ version: '0.1.0',
+ meson_version: '>= 0.59.0',
+ license: 'GPL-3.0-or-later'
+)
+
+description = 'Video player that aims to be mobile friendly. Stream videos on the go from multiple sources.'
+prettyname = 'Melon'
+
+authornickname = 'comcloudway'
+authorfullname = 'Jakob Meier'
+authoremail = 'comcloudway@ccw.icu'
+
+domainname = 'ccw'
+domainext = 'icu'
+
+gitrepo = 'https://codeberg.org/' + authornickname + '/' + prettyname
+
+python = import('python')
+py_installation = python.find_installation('python3')
+if not py_installation.found()
+ error('No valid python3 binary found')
+else
+ message('Found python3 binary')
+endif
+
+prefix = get_option('prefix') # should be /usr
+bindir = get_option('bindir') # should be /bin
+datadir = get_option('datadir') # should be /usr/share
+pkgdatadir = join_paths(prefix, datadir, meson.project_name())
+pythondir = py_installation.get_install_dir()
+localedir = join_paths(prefix, get_option('localedir'))
+
+base_id = '.'.join([domainext, domainname, prettyname])
+app_id_aspath = '/'.join([domainext, domainname, prettyname])
+app_id = base_id
+
+install_subdir(meson.project_name(), install_dir: pythondir)
+subdir('data')
+subdir('bin')
+
+# Run required post-install steps
+gnome.post_install(
+ gtk_update_icon_cache: true,
+ glib_compile_schemas: true,
+ update_desktop_database: true,
+)