Skip to content

Commit 5a31d30

Browse files
committed
1.5.3
1 parent f8b4170 commit 5a31d30

6 files changed

Lines changed: 180 additions & 43 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ EPG lze pak stáhnout z http://<adresa nebo jméno serveru>:<port (defaultně 80
3737
Na http://<adresa nebo jméno serveru>:<port (defaultně 8082)>, např. http://127.0.0.1:8082 je možné stiskem tlačítka vynutit načtení kanálů nebo vytvotvoření nové sessiony.
3838

3939
<b><u>Změny</u></b>
40+
v1.5.3 (1.6.2026)
41+
- přidaná možnost vyřazení kanálů z playlistu
42+
4043
v1.5.2 (31.5.2026)
4144
- vypnutí zobrazení varování při přístupu s neroutovatelných/privátních rozsahů
4245
- přidání možnosti nastavit whitelist klientských adres, pro který nebude vyžadována autentizace
@@ -53,6 +56,3 @@ v1.5.0 (11.4.2026)
5356

5457
v1.4.5 (7.4.2026)
5558
- oprava catchupu
56-
57-
v1.4.4 (7.4.2026)
58-
- změny API Oneplay

addon.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<addon id="script.oneplay.server" name="Oneplay Server" provider-name="waladir" version="1.5.2">
1+
<addon id="script.oneplay.server" name="Oneplay Server" provider-name="waladir" version="1.5.3">
22
<requires>
33
<import addon="xbmc.python" version="3.0.0"/>
44
<import addon="script.module.bottle"/>
@@ -15,6 +15,9 @@ Playlist: http://&lt;server&gt;:&lt;port&gt;/playlist
1515
<platform>all</platform>
1616
<source>https://github.com/waladir/script.oneplay.server</source>
1717
<news>
18+
v1.5.3 (1.6.2026)
19+
- přidaná možnost vyřazení kanálů z playlistu
20+
1821
v1.5.2 (31.5.2026)
1922
- vypnutí zobrazení varování při přístupu s neroutovatelných/privátních rozsahů
2023
- přidání možnosti nastavit whitelist klientských adres, pro který nebude vyžadována autentizace
@@ -31,9 +34,6 @@ v1.5.0 (11.4.2026)
3134

3235
v1.4.5 (7.4.2026)
3336
- oprava catchupu
34-
35-
v1.4.4 (7.4.2026)
36-
- změny API Oneplay
3737
</news>
3838
<assets>
3939
<icon>icon.png</icon>

changelog.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
v1.5.3 (1.6.2026)
2+
- přidaná možnost vyřazení kanálů z playlistu
3+
14
v1.5.2 (31.5.2026)
25
- vypnutí zobrazení varování při přístupu s neroutovatelných/privátních rozsahů
36
- přidání možnosti nastavit whitelist klientských adres, pro který nebude vyžadována autentizace
@@ -14,6 +17,3 @@ v1.5.0 (11.4.2026)
1417

1518
v1.4.5 (7.4.2026)
1619
- oprava catchupu
17-
18-
v1.4.4 (7.4.2026)
19-
- změny API Oneplay

resources/lib/channels.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,23 @@ def save_channels(channels):
8686
valid_to = int(time.time()) + 60*60*24
8787
data = json.dumps({'channels' : channels, 'valid_to' : valid_to})
8888
save_json_data({'filename' : 'channels.txt', 'description' : 'kanálů'}, data)
89+
90+
def load_diasbled_channels():
91+
disabled_channels = []
92+
data = load_json_data({'filename' : 'disabled_channels.txt', 'description' : 'zakázaných kanálů'})
93+
if data is not None:
94+
data = json.loads(data)
95+
if 'disabled_channels' in data:
96+
disabled_channels = data['disabled_channels']
97+
return disabled_channels
98+
99+
def save_disabled_channels(disabled_channels):
100+
channels = load_channels()
101+
for channel in channels:
102+
if channel in disabled_channels:
103+
channels[channel]['visible'] = False
104+
else:
105+
channels[channel]['visible'] = True
106+
save_channels(channels)
107+
data = json.dumps({'disabled_channels' : disabled_channels})
108+
save_json_data({'filename' : 'disabled_channels.txt', 'description' : 'zakázaných kanálů'}, data)

resources/lib/web.py

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010

1111
from resources.lib.session import load_session
12-
from resources.lib.channels import load_channels
12+
from resources.lib.channels import load_channels, load_diasbled_channels, save_disabled_channels
1313
from resources.lib.epg import get_epg, load_epg, get_live_epg, get_channel_epg
1414
from resources.lib.stream import get_live, get_archive
1515
from resources.lib.utils import get_config_value, get_script_path, get_version, check_client_network, check_ip_whitelist
@@ -98,21 +98,22 @@ def playlist():
9898
base_url = get_base_url()
9999
output = '#EXTM3U x-tvg-url="' + base_url + '/epg"\n'
100100
for channel in channels:
101-
if channels[channel]['logo'] == None:
102-
logo = ''
103-
else:
104-
logo = channels[channel]['logo']
105-
if get_config_value('odstranit_hd') == 1 or get_config_value('odstranit_hd') == '1' or get_config_value('odstranit_hd') == 'true':
106-
channel_name = channels[channel]['name'].replace(' HD', '')
107-
else:
108-
channel_name = channels[channel]['name']
109-
output += '#EXTINF:-1 provider="Oneplay" tvg-chno="' + str(channels[channel]['channel_number']) + '" tvg-name="' + channel_name + '" tvg-logo="' + logo + '" catchup-days="7" catchup="shift", ' + channel_name + '\n'
110-
output += '#KODIPROP:inputstream.adaptive.stream_headers=' + urlencode(headers) + '\n'
111-
output += '#KODIPROP:inputstream.adaptive.manifest_headers=' + urlencode(headers) + '\n'
112-
if get_config_value('pouzivat_cisla_kanalu') == None or get_config_value('pouzivat_cisla_kanalu') == 0 or get_config_value('pouzivat_cisla_kanalu') == '0' or get_config_value('pouzivat_cisla_kanalu') == 'false':
113-
output += base_url + '/play/' + quote(channel_name.replace('/', 'sleš')) + '.m3u8\n'
114-
else:
115-
output += base_url + '/play_num/' + str(channels[channel]['channel_number']) + '.m3u8\n'
101+
if channels[channel]['visible'] == True:
102+
if channels[channel]['logo'] == None:
103+
logo = ''
104+
else:
105+
logo = channels[channel]['logo']
106+
if get_config_value('odstranit_hd') == 1 or get_config_value('odstranit_hd') == '1' or get_config_value('odstranit_hd') == 'true':
107+
channel_name = channels[channel]['name'].replace(' HD', '')
108+
else:
109+
channel_name = channels[channel]['name']
110+
output += '#EXTINF:-1 provider="Oneplay" tvg-chno="' + str(channels[channel]['channel_number']) + '" tvg-name="' + channel_name + '" tvg-logo="' + logo + '" catchup-days="7" catchup="shift", ' + channel_name + '\n'
111+
output += '#KODIPROP:inputstream.adaptive.stream_headers=' + urlencode(headers) + '\n'
112+
output += '#KODIPROP:inputstream.adaptive.manifest_headers=' + urlencode(headers) + '\n'
113+
if get_config_value('pouzivat_cisla_kanalu') == None or get_config_value('pouzivat_cisla_kanalu') == 0 or get_config_value('pouzivat_cisla_kanalu') == '0' or get_config_value('pouzivat_cisla_kanalu') == 'false':
114+
output += base_url + '/play/' + quote(channel_name.replace('/', 'sleš')) + '.m3u8\n'
115+
else:
116+
output += base_url + '/play_num/' + str(channels[channel]['channel_number']) + '.m3u8\n'
116117
response.content_type = 'text/plain; charset=UTF-8'
117118
return output
118119

@@ -125,19 +126,20 @@ def playlist_tvheadend():
125126
if ffmpeg == None or len(ffmpeg) == 0:
126127
ffmpeg = '/usr/bin/ffmpeg'
127128
for channel in channels:
128-
if channels[channel]['logo'] == None:
129-
logo = ''
130-
else:
131-
logo = channels[channel]['logo']
132-
if get_config_value('odstranit_hd') == 1 or get_config_value('odstranit_hd') == '1' or get_config_value('odstranit_hd') == 'true':
133-
channel_name = channels[channel]['name'].replace(' HD', '')
134-
else:
135-
channel_name = channels[channel]['name']
136-
output += '#EXTINF:-1 provider="Oneplay" tvg-chno="' + str(channels[channel]['channel_number']) + '" tvg-name="' + channel_name + '" tvg-logo="' + logo + '", ' + channel_name + '\n'
137-
if get_config_value('pouzivat_cisla_kanalu') == None or get_config_value('pouzivat_cisla_kanalu') == 0 or get_config_value('pouzivat_cisla_kanalu') == '0' or get_config_value('pouzivat_cisla_kanalu') == 'false':
138-
output += 'pipe://' + ffmpeg + ' -loglevel error -fflags +genpts -i "' + base_url + '/play/' + quote(channel_name.replace('/', 'sleš')) + '.m3u8" -f mpegts -c copy -vcodec copy -acodec copy -metadata service_provider=Oneplay -metadata service_name="' + channel_name + '" pipe:1\n'
139-
else:
140-
output += 'pipe://' + ffmpeg + ' -loglevel error -fflags +genpts -i "' + base_url + '/play_num/' + str(channels[channel]['channel_number']) + '.m3u8" -f mpegts -c copy -vcodec copy -acodec copy -metadata service_provider=Oneplay -metadata service_name="' + channel_name + '" pipe:1\n'
129+
if channels[channel]['visible'] == True:
130+
if channels[channel]['logo'] == None:
131+
logo = ''
132+
else:
133+
logo = channels[channel]['logo']
134+
if get_config_value('odstranit_hd') == 1 or get_config_value('odstranit_hd') == '1' or get_config_value('odstranit_hd') == 'true':
135+
channel_name = channels[channel]['name'].replace(' HD', '')
136+
else:
137+
channel_name = channels[channel]['name']
138+
output += '#EXTINF:-1 provider="Oneplay" tvg-chno="' + str(channels[channel]['channel_number']) + '" tvg-name="' + channel_name + '" tvg-logo="' + logo + '", ' + channel_name + '\n'
139+
if get_config_value('pouzivat_cisla_kanalu') == None or get_config_value('pouzivat_cisla_kanalu') == 0 or get_config_value('pouzivat_cisla_kanalu') == '0' or get_config_value('pouzivat_cisla_kanalu') == 'false':
140+
output += 'pipe://' + ffmpeg + ' -loglevel error -fflags +genpts -i "' + base_url + '/play/' + quote(channel_name.replace('/', 'sleš')) + '.m3u8" -f mpegts -c copy -vcodec copy -acodec copy -metadata service_provider=Oneplay -metadata service_name="' + channel_name + '" pipe:1\n'
141+
else:
142+
output += 'pipe://' + ffmpeg + ' -loglevel error -fflags +genpts -i "' + base_url + '/play_num/' + str(channels[channel]['channel_number']) + '.m3u8" -f mpegts -c copy -vcodec copy -acodec copy -metadata service_provider=Oneplay -metadata service_name="' + channel_name + '" pipe:1\n'
141143
response.content_type = 'text/plain; charset=UTF-8'
142144
return output
143145

@@ -212,6 +214,16 @@ def config():
212214
response.content_type = 'application/json'
213215
return json.dumps(config)
214216

217+
@route('/channel/<channel>/<status>')
218+
def channel(channel, status):
219+
disabled_channels = load_diasbled_channels()
220+
if status == 'disable' and channel not in disabled_channels:
221+
disabled_channels.append(channel)
222+
save_disabled_channels(disabled_channels)
223+
elif status == 'enable' and channel in disabled_channels:
224+
disabled_channels.remove(channel)
225+
save_disabled_channels(disabled_channels)
226+
215227
@route('/')
216228
@post('/')
217229
def page():
@@ -240,14 +252,15 @@ def page():
240252
channel_name = channels[channel]['name'].replace(' HD', '')
241253
else:
242254
channel_name = channels[channel]['name']
243-
playlist.append({'name' : channel_name, 'url' : base_url_with_auth + '/play/' + quote(channel_name.replace('/', 'sleš')) + '.m3u8', 'slug' : quote(channel_name.replace('/', 'sleš')) + '.m3u8', 'logo' : channels[channel]['logo'], 'channel_id' : channel, 'liveOnly' : channels[channel].get('liveOnly', False)})
255+
playlist.append({'name' : channel_name, 'url' : base_url_with_auth + '/play/' + quote(channel_name.replace('/', 'sleš')) + '.m3u8', 'slug' : quote(channel_name.replace('/', 'sleš')) + '.m3u8', 'logo' : channels[channel]['logo'], 'channel_id' : channel, 'liveOnly' : channels[channel].get('liveOnly', False), 'visible' : channels[channel]['visible']})
244256
else:
245257
for channel in channels:
246258
if get_config_value('odstranit_hd') == 1 or get_config_value('odstranit_hd') == '1' or get_config_value('odstranit_hd') == 'true':
247259
channel_name = channels[channel]['name'].replace(' HD', '')
248260
else:
249261
channel_name = channels[channel]['name']
250-
playlist.append({'name' : channel_name, 'url' : base_url_with_auth + '/play_num/' + str(channels[channel]['channel_number']) + '.m3u8', 'slug' : quote(channel_name.replace('/', 'sleš')) + '.m3u8', 'logo' : channels[channel]['logo'], 'channel_id' : channel, 'liveOnly' : channels[channel].get('liveOnly', False)})
262+
playlist.append({'name' : channel_name, 'url' : base_url_with_auth + '/play_num/' + str(channels[channel]['channel_number']) + '.m3u8', 'slug' : quote(channel_name.replace('/', 'sleš')) + '.m3u8', 'logo' : channels[channel]['logo'], 'channel_id' : channel, 'liveOnly' : channels[channel].get('liveOnly', False), 'visible' : channels[channel]['visible']})
263+
print(channels[channel]['visible'])
251264
TEMPLATE_PATH.append(os.path.join(get_script_path(), 'resources', 'templates'))
252265
auth_enabled = bool(get_config_value('auth_user') and get_config_value('auth_pass'))
253266
return template('form.tpl', version = get_version(), message = message, warning = warning, playlist_url = playlist_url, playlist_tvheadend_url = playlist_tvheadend_url, epg_url = epg_url, playlist = playlist, auth_enabled = auth_enabled, player_enabled = player_enabled)

resources/templates/form.tpl

Lines changed: 106 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,77 @@
372372
.actions { flex-direction: column; }
373373
.btn { width: 100%; justify-content: center; }
374374
}
375-
</style>
375+
376+
/* Seskupení loga a slideru pod sebou */
377+
.ch-left-side {
378+
display: flex;
379+
flex-direction: column;
380+
align-items: center;
381+
justify-content: center;
382+
gap: 4px; /* Mezera mezi logem a sliderem */
383+
min-width: 60px; /* Přizpůsobte podle běžné šířky vašich log */
384+
}
385+
386+
/* Kontejner pro miniaturní přepínač */
387+
.ch-status-mini {
388+
display: flex;
389+
justify-content: center;
390+
align-items: center;
391+
width: 100%;
392+
padding-top: 2px;
393+
}
394+
395+
/* Ultra malý switch */
396+
.switch-mini {
397+
position: relative;
398+
display: inline-block;
399+
width: 26px;
400+
height: 14px;
401+
}
402+
403+
.switch-mini input {
404+
opacity: 0;
405+
width: 0;
406+
height: 0;
407+
}
408+
409+
.slider-mini {
410+
position: absolute;
411+
cursor: pointer;
412+
top: 0; left: 0; right: 0; bottom: 0;
413+
background-color: #555;
414+
transition: .2s;
415+
}
416+
417+
.slider-mini:before {
418+
position: absolute;
419+
content: "";
420+
height: 10px;
421+
width: 10px;
422+
left: 2px;
423+
bottom: 2px;
424+
background-color: white;
425+
transition: .2s;
426+
}
427+
428+
/* Aktivní zelený stav */
429+
input:checked + .slider-mini {
430+
background-color: #4CAF50;
431+
}
432+
433+
/* Posun kolečka v mini verzi */
434+
input:checked + .slider-mini:before {
435+
transform: translateX(12px);
436+
}
437+
438+
.slider-mini.round {
439+
border-radius: 14px;
440+
}
441+
442+
.slider-mini.round:before {
443+
border-radius: 50%;
444+
}
445+
</style>
376446
</head>
377447
<body>
378448
<div class="container">
@@ -442,7 +512,19 @@
442512
<div class="channel-grid">
443513
% for item in playlist:
444514
<div class="channel-card" data-channel-id="{{ item['channel_id'] }}" onclick="playChannel('{{ item['slug'] }}', '{{ item['name'] }}', this)">
445-
<img src="{{ item['logo'] }}" alt="{{ item['name'] }}" loading="lazy">
515+
<div class="ch-left-side">
516+
<img src="{{ item['logo'] }}" alt="{{ item['name'] }}" loading="lazy">
517+
518+
<div class="ch-status-mini" onclick="event.stopPropagation();" title="Zobrazit v playlistu">
519+
<label class="switch-mini">
520+
<input type="checkbox"
521+
class="channel-toggle"
522+
data-channel-id="{{ item['channel_id'] }}"
523+
{{ 'checked' if item.get('visible', True) else '' }}>
524+
<span class="slider-mini round"></span>
525+
</label>
526+
</div>
527+
</div>
446528
<div class="ch-meta">
447529
<span class="ch-name">{{ item['name'] }}</span>
448530
<div class="ch-epg" style="display:none"></div>
@@ -968,6 +1050,28 @@
9681050
card.style.display = name.includes(query) ? '' : 'none';
9691051
});
9701052
}
1053+
1054+
document.addEventListener('DOMContentLoaded', function() {
1055+
const toggles = document.querySelectorAll('.channel-toggle');
1056+
1057+
toggles.forEach(toggle => {
1058+
toggle.addEventListener('change', function() {
1059+
const channelId = this.getAttribute('data-channel-id');
1060+
const status = this.checked ? 'enable' : 'disable';
1061+
1062+
fetch(`/channel/${channelId}/${status}`, {
1063+
method: 'GET'
1064+
})
1065+
.catch(error => {
1066+
console.error('Chyba:', error);
1067+
this.checked = !this.checked;
1068+
alert('Nepodařilo se změnit stav kanálu.');
1069+
});
1070+
});
1071+
});
1072+
});
1073+
9711074
</script>
1075+
9721076
</body>
9731077
</html>

0 commit comments

Comments
 (0)