Skip to content

Commit c0d65e1

Browse files
committed
Initial commit
0 parents  commit c0d65e1

23 files changed

+3200
-0
lines changed

Makefile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
all: buildmo
2+
3+
buildmo:
4+
@echo "Building the mo files"
5+
# WARNING: the second sed below will only works correctly with the languages that don't contain "-"
6+
for file in `ls po/*.po`; do \
7+
lang=`echo $$file | sed 's@po/@@' | sed 's/\.po//' | sed 's/hypnotix-//'`; \
8+
install -d usr/share/locale/$$lang/LC_MESSAGES/; \
9+
msgfmt -o usr/share/locale/$$lang/LC_MESSAGES/hypnotix.mo $$file; \
10+
done \
11+
12+
clean:
13+
rm -rf usr/share/locale

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Hypnotix
2+
3+
An IPTV app.

debian/changelog

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
hypnotix (1.0.0) ulyana; urgency=low
2+
3+
* Initial release
4+
5+
-- Clement Lefebvre <[email protected]> Tue, 26 Oct 2020 21:38:00 +0000
6+

debian/compat

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
9

debian/control

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Source: hypnotix
2+
Section: admin
3+
Priority: optional
4+
Maintainer: Linux Mint <[email protected]>
5+
Build-Depends: debhelper (>= 9)
6+
Standards-Version: 3.9.5
7+
8+
Package: hypnotix
9+
Architecture: all
10+
Depends: gir1.2-xapp-1.0,
11+
python3,
12+
python3-gi,
13+
python3-setproctitle,
14+
xapps-common,
15+
${misc:Depends},
16+
Description: IPTV Player
17+
Watch TV by streaming from M3U sources.

debian/copyright

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2+
Upstream-Name: hypnotix
3+
Upstream-Contact: Linux Mint <[email protected]>
4+
Source: https://github.com/linuxmint/hypnotix
5+
6+
Files: *
7+
Copyright: 2020 Linux Mint <[email protected]>
8+
License: GPL-3+
9+
This program is free software; you can redistribute it and/or modify
10+
it under the terms of the GNU General Public License as published by
11+
the Free Software Foundation; either version 3 of the License, or
12+
(at your option) any later version.
13+
.
14+
This program is distributed in the hope that it will be useful,
15+
but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
GNU General Public License for more details.
18+
.
19+
On Debian systems, the complete text of the GNU General
20+
Public License can be found in `/usr/share/common-licenses/GPL'

debian/install

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
usr

debian/postinst

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/sh
2+
set -e
3+
4+
case "$1" in
5+
configure)
6+
if which glib-compile-schemas >/dev/null 2>&1
7+
then
8+
glib-compile-schemas /usr/share/glib-2.0/schemas
9+
fi
10+
;;
11+
12+
abort-upgrade|abort-remove|abort-deconfigure)
13+
14+
;;
15+
16+
*)
17+
echo "postinst called with unknown argument \`$1'" >&2
18+
exit 1
19+
;;
20+
esac

debian/rules

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/make -f
2+
3+
DEB_VERSION := $(shell dpkg-parsechangelog | egrep '^Version:' | cut -f 2 -d ' ')
4+
5+
%:
6+
dh ${@}
7+
8+
# Inject version number in the code
9+
override_dh_installdeb:
10+
dh_installdeb
11+
for pkg in $$(dh_listpackages -i); do \
12+
find debian/$$pkg -type f -exec sed -i -e s/__DEB_VERSION__/$(DEB_VERSION)/g {} +; \
13+
done

debian/source/format

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.0 (native)

generate_desktop_files

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/python3
2+
3+
DOMAIN = "hypnotix"
4+
PATH = "/usr/share/locale"
5+
6+
import os
7+
import gettext
8+
from mintcommon import additionalfiles
9+
10+
os.environ['LANGUAGE'] = "en_US.UTF-8"
11+
gettext.install(DOMAIN, PATH)
12+
13+
prefix = "[Desktop Entry]\n"
14+
15+
suffix = """Exec=hypnotix
16+
Icon=hypnotix
17+
Terminal=false
18+
Type=Application
19+
Encoding=UTF-8
20+
Categories=AudioVideo;Video;Player;TV;
21+
StartupNotify=false
22+
"""
23+
24+
additionalfiles.generate(DOMAIN, PATH, "usr/share/applications/hypnotix.desktop", prefix, _("Hypnotix"), _("Watch TV"), suffix)

makepot

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
intltool-extract --type=gettext/glade usr/share/hypnotix/hypnotix.ui
3+
intltool-extract --type=gettext/glade usr/share/hypnotix/shortcuts.ui
4+
xgettext --language=Python --keyword=_ --keyword=N_ --output=hypnotix.pot usr/lib/hypnotix/*.py generate_desktop_files usr/share/hypnotix/hypnotix.ui.h usr/share/hypnotix/shortcuts.ui.h
5+
rm -f usr/share/hypnotix/*.ui.h

po/hypnotix-fr.po

Whitespace-only changes.

test

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash
2+
sudo rm -rf /usr/lib/hypnotix
3+
sudo rm -rf /usr/share/hypnotix
4+
sudo cp -R usr /
5+
hypnotix

usr/bin/hypnotix

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#!/bin/sh
2+
/usr/lib/hypnotix/hypnotix.py &

usr/lib/hypnotix/common.py

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/usr/bin/python3
2+
import configparser
3+
import gi
4+
import os
5+
import requests
6+
import shutil
7+
import string
8+
import threading
9+
import re
10+
from gi.repository import GObject
11+
from random import choice
12+
13+
# M3U parsing regex
14+
PARAMS = re.compile(r'(\S+)="(.*?)"')
15+
EXTINF = re.compile(r'^#EXTINF:(?P<duration>-?\d+?) ?(?P<params>.*),(?P<title>.*?)$')
16+
PROVIDERS_PATH = os.path.expanduser("~/.hypnotix/providers")
17+
18+
# Used as a decorator to run things in the background
19+
def _async(func):
20+
def wrapper(*args, **kwargs):
21+
thread = threading.Thread(target=func, args=args, kwargs=kwargs)
22+
thread.daemon = True
23+
thread.start()
24+
return thread
25+
return wrapper
26+
27+
# Used as a decorator to run things in the main loop, from another thread
28+
def idle(func):
29+
def wrapper(*args):
30+
GObject.idle_add(func, *args)
31+
return wrapper
32+
33+
def slugify(string):
34+
"""
35+
Normalizes string, converts to lowercase, removes non-alpha characters,
36+
and converts spaces to hyphens.
37+
"""
38+
return "".join(x.lower() for x in string if x.isalnum())
39+
40+
class Provider():
41+
def __init__(self, name, url):
42+
self.name = name
43+
self.path = os.path.join(PROVIDERS_PATH, slugify(name))
44+
self.url = url
45+
self.groups = []
46+
self.channels = []
47+
48+
class Group():
49+
def __init__(self, name):
50+
self.name = name
51+
self.channels = []
52+
53+
class Channel():
54+
def __init__(self, info):
55+
self.info = info
56+
self.id = None
57+
self.name = None
58+
self.logo = None
59+
self.group_title = None
60+
self.title = None
61+
self.url = None
62+
match = EXTINF.fullmatch(info)
63+
if match != None:
64+
res = match.groupdict()
65+
if 'params' in res:
66+
params = dict(PARAMS.findall(res['params']))
67+
if "tvg-name" in params and params['tvg-name'].strip() != "":
68+
self.name = params['tvg-name'].strip()
69+
if "tvg-logo" in params and params['tvg-logo'].strip() != "":
70+
self.logo = params['tvg-logo'].strip()
71+
if "group-title" in params and params['group-title'].strip() != "":
72+
self.group_title = params['group-title'].strip()
73+
if 'title' in res:
74+
self.title = res['title']
75+
76+
class Manager():
77+
78+
def __init__(self):
79+
os.system("mkdir -p '%s'" % PROVIDERS_PATH)
80+
81+
def download_playlist(self, provider):
82+
success = False
83+
try:
84+
response = requests.get(provider.url, timeout=10)
85+
if response.status_code == 200:
86+
print("Download success")
87+
try:
88+
source = response.content.decode("UTF-8")
89+
except UnicodeDecodeError as e:
90+
source = response.content.decode("latin1")
91+
if (source.count("#EXTM3U") > 0 and source.count("#EXTINF") > 0):
92+
print("Content looks legit")
93+
with open(provider.path, "w") as file:
94+
file.write(source)
95+
success = True
96+
except Exception as e:
97+
print(e)
98+
finally:
99+
return success
100+
101+
def load_channels(self, provider):
102+
with open(provider.path, "r") as file:
103+
channel = None
104+
group = None
105+
groups = {}
106+
for line in file:
107+
line = line.strip()
108+
if line.startswith("#EXTM3U"):
109+
continue
110+
if line.startswith("#EXTINF"):
111+
channel = Channel(line)
112+
continue
113+
if "://" in line:
114+
if channel == None:
115+
continue
116+
if channel.url != None:
117+
# We already found the URL, skip the line
118+
continue
119+
if channel.name == None or "***" in channel.name:
120+
continue
121+
channel.url = line
122+
provider.channels.append(channel)
123+
if channel.group_title != None and channel.group_title.strip() != "":
124+
if group == None or group.name != channel.group_title:
125+
if channel.group_title in groups.keys():
126+
group = groups[channel.group_title]
127+
else:
128+
group = Group(channel.group_title)
129+
provider.groups.append(group)
130+
groups[channel.group_title] = group
131+
group.channels.append(channel)
132+

0 commit comments

Comments
 (0)