Commit initial du projet serverIPTV
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
||||
.DS_Store
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
# Contenu téléchargé / généré, pas du code source
|
||||
playlists/
|
||||
playlists_download/
|
||||
logos/
|
||||
letsencrypt/
|
||||
epg/epg.xml
|
||||
static/config_init.db
|
||||
static/device_snapshots/
|
||||
|
||||
# Config locale spécifique à cette machine
|
||||
.claude/
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
FROM python
|
||||
RUN apt-get update -y
|
||||
ENV LANG C.UTF-8
|
||||
ENV LC_ALL C.UTF-8
|
||||
|
||||
RUN apt-get -y install python3-pip
|
||||
RUN apt-get install --reinstall ca-certificates
|
||||
|
||||
RUN pip3 install aiohttp
|
||||
RUN pip3 install aiohttp-session
|
||||
RUN pip3 install aiomcache
|
||||
RUN pip3 install python-socketio
|
||||
RUN pip3 install pyOpenSSL
|
||||
RUN pip3 install tornado
|
||||
RUN pip3 install aiohttp-cors
|
||||
RUN pip3 install requests
|
||||
RUN pip3 install pillow
|
||||
RUN pip3 install pymongo
|
||||
RUN pip3 install aiofiles
|
||||
RUN pip3 install wikipedia-api
|
||||
RUN pip3 install beautifulsoup4
|
||||
|
||||
EXPOSE 443
|
||||
|
||||
WORKDIR "/home"
|
||||
|
||||
CMD ["python3", "server.py"]
|
||||
@@ -0,0 +1,37 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
mongodb:
|
||||
image: mongo:latest
|
||||
container_name: mongodb
|
||||
ports:
|
||||
- "27017:27017"
|
||||
environment:
|
||||
MONGO_INITDB_ROOT_USERNAME: root
|
||||
MONGO_INITDB_ROOT_PASSWORD: example
|
||||
volumes:
|
||||
- mongodb_data:/data/db
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
python-app:
|
||||
build: .
|
||||
container_name: serverIPTV-container
|
||||
depends_on:
|
||||
- mongodb
|
||||
environment:
|
||||
- MONGO_URI=mongodb://root:example@mongodb:27017/?authSource=admin
|
||||
networks:
|
||||
- app-network
|
||||
volumes:
|
||||
- /etc/letsencrypt/:/home/letsencrypt/:rw
|
||||
- /home/ubuntu/serverIPTV/:/home/:rw
|
||||
ports:
|
||||
- "5023:5023"
|
||||
- "443:443"
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
|
||||
volumes:
|
||||
mongodb_data:
|
||||
@@ -0,0 +1,60 @@
|
||||
|
||||
|
||||
# Generic functions
|
||||
def findInfo(tag, s):
|
||||
end = '"'
|
||||
if (s.find(tag)>-1):
|
||||
t = s[s.find(tag)+len(tag)+2:s.rfind(end)]
|
||||
return t[0:t.find(end)]
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
|
||||
|
||||
def download_logo():
|
||||
|
||||
path_filename = 'source.m3u'
|
||||
|
||||
# Process the m3u file
|
||||
f = open(path_filename, "r", encoding="utf8")
|
||||
Lines = f.readlines()
|
||||
print('ouverture fichier', flush=True)
|
||||
channel = {}
|
||||
separator = '--------'
|
||||
topic = ''
|
||||
num_channel = 0
|
||||
for line in Lines:
|
||||
#print(line,flush=True)
|
||||
|
||||
if separator in line:
|
||||
topic = line[10:]
|
||||
topic = topic.replace(separator,'')
|
||||
|
||||
if len(channel)>0 and line[0:4]=="http":
|
||||
|
||||
print ('download ' + channel['urlLogo'])
|
||||
#new_channels.append(channel)
|
||||
#print(channel, flush=True)
|
||||
|
||||
if line[0:4] == "#EXT":
|
||||
channel = {}
|
||||
|
||||
# DEFINE CHANNEL NAME
|
||||
name_channel = line[line.rfind(',')+1:].strip('\n')
|
||||
|
||||
channel_exist = False
|
||||
if len(name_channel)>0:
|
||||
channel = {"Name":name_channel, "Topic":topic, "urlLogo" : findInfo('tvg-logo', line)}
|
||||
num_channel += 1
|
||||
channel_exist = True
|
||||
|
||||
if channel_exist == False:
|
||||
name_channel = line[line.rfind('|')+2:].strip('\n')
|
||||
if len(name_channel) > 0:
|
||||
channel = {"Name":name_channel, "Topic":topic, "urlLogo" : findInfo('tvg-logo', line)}
|
||||
|
||||
|
||||
result = {'num_channel': num_channel}
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,21 @@
|
||||
import requests
|
||||
|
||||
url = "http://xfvjwqjn.duperab.xyz/xmltv.php?username=VQNRZG7N&password=W3NFBDXY"
|
||||
|
||||
headers = {
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
"Accept-Language": "en-US,en;q=0.9,fr;q=0.8,fr-FR;q=0.7",
|
||||
"Cache-Control": "max-age=0",
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0"
|
||||
}
|
||||
|
||||
response = requests.get(url, headers=headers, verify=False)
|
||||
|
||||
if response.status_code == 200 and response.content:
|
||||
with open("epg.xml", "wb") as f:
|
||||
f.write(response.content)
|
||||
print("Download successful: epg.xml")
|
||||
else:
|
||||
print(f"Failed to download. Status code: {response.status_code}, Content length: {len(response.content)}")
|
||||
+1344
File diff suppressed because it is too large
Load Diff
+584
@@ -0,0 +1,584 @@
|
||||
|
||||
var quote = String.fromCharCode(39)
|
||||
var list_playlist = []
|
||||
var result_search = []
|
||||
|
||||
|
||||
|
||||
var socket = io('https://iptv.mrk.ovh', {
|
||||
secure: true,
|
||||
rejectUnauthorized: false // Only for self-signed certificates
|
||||
});
|
||||
|
||||
socket.on('connect', function() {
|
||||
$('#sidClient').val(socket.id)
|
||||
console.log("client connected sid = " + $('#sidClient').val())
|
||||
});
|
||||
|
||||
|
||||
socket.on('communication', function(data) {
|
||||
console.log('communication', data)
|
||||
// RECEPTION RETURN WARNING
|
||||
if (data['typeCommand'] == 'warning') {
|
||||
$('#warning').html(data['payload'])
|
||||
$('#warning').show('slow')
|
||||
setTimeout(() => { $('#warning').hide('slow') }, 5000);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
$(window).on('scroll resize', lazyLoad);
|
||||
|
||||
// INITIALISAITON DES TABS
|
||||
$('#tab_home').show()
|
||||
update_list_playlist()
|
||||
//get_search_channel()
|
||||
$('#tab_search').hide()
|
||||
$('#tab_playlist').hide()
|
||||
$('#tab_devices').hide()
|
||||
|
||||
|
||||
// CLICK TAB HOME
|
||||
$('#display_tab_home').click(function(){
|
||||
var triggerTab = new bootstrap.Tab($('#display_tab_home'));
|
||||
triggerTab.show();
|
||||
$('#tab_home').show()
|
||||
$('#tab_search').hide()
|
||||
$('#tab_playlist').hide()
|
||||
$('#tab_devices').hide()
|
||||
get_search_channel()
|
||||
})
|
||||
|
||||
|
||||
// CLICK TAB PLAYLIST
|
||||
$('#display_tab_playlist').click(function(){
|
||||
var triggerTab = new bootstrap.Tab($('#display_tab_playlist'));
|
||||
triggerTab.show();
|
||||
$('#tab_home').hide()
|
||||
$('#tab_search').hide()
|
||||
$('#tab_playlist').show()
|
||||
$('#tab_devices').hide()
|
||||
})
|
||||
|
||||
// CLICK TAB DEVICES
|
||||
$('#display_tab_devices').click(function(){
|
||||
var triggerTab = new bootstrap.Tab($('#display_tab_devices'));
|
||||
triggerTab.show();
|
||||
$('#tab_home').hide()
|
||||
$('#tab_search').hide()
|
||||
$('#tab_playlist').hide()
|
||||
$('#tab_devices').show()
|
||||
|
||||
})
|
||||
|
||||
// CLICK TAB SEARCH
|
||||
$('#display_tab_search').click(function(){
|
||||
var triggerTab = new bootstrap.Tab($('#display_tab_devices'));
|
||||
triggerTab.show();
|
||||
$('#tab_home').hide()
|
||||
$('#tab_search').show()
|
||||
$('#tab_playlist').hide()
|
||||
$('#tab_devices').hide()
|
||||
|
||||
})
|
||||
|
||||
// UPDATE TAB DEVICES
|
||||
updateListDevices()
|
||||
setInterval(updateListDevices, 10000)
|
||||
|
||||
|
||||
$('#btn_search').click(function(){
|
||||
get_search_channel($('#input_search').val())
|
||||
})
|
||||
|
||||
$('#btn_initdb').click(function(){
|
||||
$.get('init_db').done(function(data){
|
||||
window.location.href = "/"
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
function isInViewport(element) {
|
||||
const rect = element[0].getBoundingClientRect();
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
||||
);
|
||||
}
|
||||
|
||||
function lazyLoad() {
|
||||
$('.lazy').each(function () {
|
||||
const img = $(this);
|
||||
if (isInViewport(img) && !img.hasClass('loaded')) {
|
||||
img.attr('src', img.data('src')); // Load the image
|
||||
img.on('load', function () {
|
||||
img.addClass('loaded'); // Add loaded class for fade-in
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// DISPLAY CHANNELS
|
||||
function displayResult() {
|
||||
|
||||
$('#listChannels').show()
|
||||
|
||||
if ($('#typeDisplay').val() == 'list') html = '<ul class="list-group">'
|
||||
else html = '<div class="d-flex flex-wrap">'
|
||||
result_search.forEach(element => {
|
||||
var id_channel = element['id_channel']
|
||||
var name_channel = element['Name']
|
||||
var url_channel = element['url']
|
||||
var url_channel_macos = url_channel.replace("http://", 'rtmp://')
|
||||
//url_channel_macos = url_channel.replace("https://", 'rtmp://')
|
||||
if ($('#typeDisplay').val() == 'list') {
|
||||
html += `<li data-url_channel="${url_channel}" data-name_channel="${name_channel}" class="btn_play_media lineTable_device list-group-item"><img src="get_logo?id_channel=${id_channel}" style="height:30px"> <span class="p-3">${name_channel}</span></li>`
|
||||
} else {
|
||||
html += `<div data-id_channel="${id_channel}" class="m-2 card" style="width: 18rem;"> \
|
||||
<img class="card-img-top mx-auto d-block mt-2" src="get_logo?id_channel=${id_channel}" alt="Card image cap" style='width:100px'> \
|
||||
<div class="card-body"> \
|
||||
<p class="btn_play_media card-text">${name_channel}</p> \
|
||||
<button data-display_url="${url_channel}" class="btn_display_url btn btn-info">url</button> \
|
||||
<a class="btn btn-primary" href="vlc://${url_channel_macos}">vlc ios</a> \
|
||||
</div> \
|
||||
</div>`
|
||||
}
|
||||
})
|
||||
if ($('#typeDisplay').val() == 'list') html += '</ul>'
|
||||
else html += '</div>'
|
||||
$('#listChannels').html(html)
|
||||
|
||||
$('.btn_play_media').click(function(){
|
||||
var name = $(this).data('name_channel')
|
||||
var url = $(this).data('url_channel')
|
||||
console.log('play media', name, url)
|
||||
socket.emit('communication', {fromSidDevice : $('#sidClient').val(), toSidDevice: $('#sidDevice').val(), typeCommand : "on", payload: {name:name, url:url}, "packet":0});
|
||||
})
|
||||
$('.btn_display_url').click(function() {
|
||||
var url = $(this).data('display_url')
|
||||
alert(url)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// GET RESULT SEARCH
|
||||
function get_search_channel() {
|
||||
var keyword = $('#input_search').val()
|
||||
if (keyword.length>0) {
|
||||
$.post('list_channel', {keyword:encodeURIComponent(keyword)}).done(function(data){
|
||||
$('#list_channels').html('')
|
||||
var html =''
|
||||
console.log(data)
|
||||
result_search = JSON.parse(data)
|
||||
displayResult()
|
||||
lazyLoad();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// UPDATE LIST PLAYLIST
|
||||
function update_list_playlist() {
|
||||
|
||||
$.get('get_list_playlists').done(function(data){
|
||||
console.log(data)
|
||||
$('#list_playlist').html('')
|
||||
var html = '<table class="table"> \
|
||||
<thead>\
|
||||
<tr>\
|
||||
<th scope="col">#</th>\
|
||||
<th scope="col">Title</th>\
|
||||
<th scope="col">End Date</th>\
|
||||
<th scope="col">Num channels</th>\
|
||||
<th scope="col"></th>\
|
||||
<th scope="col"></th>\
|
||||
<!--<th scope="col">Sid</th>-->\
|
||||
</tr>\
|
||||
</thead>\
|
||||
<tbody>'
|
||||
|
||||
|
||||
var dataJSON = JSON.parse(data)
|
||||
//list_playlist = dataJSON
|
||||
|
||||
for (var i=0;i<dataJSON.length;i++) {
|
||||
console.log(dataJSON[i])
|
||||
var id_playlist = dataJSON[i].id_playlist
|
||||
var state_playlist = dataJSON[i].activation
|
||||
console.log('state playlist', state_playlist)
|
||||
var html_state = ''
|
||||
if (state_playlist == 1) {
|
||||
html_state = 'class="btn btn-secondary btn-sm" >Active'
|
||||
} else {
|
||||
html_state = 'class="btn btn-outline-secondary btn-sm" >Inactive'
|
||||
}
|
||||
html += `<tr class="lineTable_device" id="zone_${dataJSON[i].title_playlist}">\
|
||||
<th scope="row">${i.toString()}</th>\
|
||||
<td>${dataJSON[i].title_playlist}</td>\
|
||||
<td>${dataJSON[i].date_end_playlist}</td>\
|
||||
<td>${dataJSON[i].num_channels}</td>\
|
||||
<!--<td style="color:darkblue" id="zone_edit_playlist_${id_playlist}"><div class="d-flex flex-row"><div><i class="fas fa-edit"></i></div><div class="d-none d-lg-block" style="margin-left: 5px;">Edit</div></div></td>-->\
|
||||
<td style="color:red" data-param1="id_playlist" id="zone_delete_playlist_${id_playlist}"><div class="d-flex flex-row"><div><i class="fas fa-trash-alt"></i></div><div class="d-none d-lg-block" style="margin-left: 5px;">Delete</div></div></td>\
|
||||
<!--<td>'+dataJSON[i].id_playlist+'</td>-->\
|
||||
<input id="state_activation_${id_playlist}" type="hidden" val=""> \
|
||||
<td><div id="btn_activation_playlist_${id_playlist}" ${html_state}</div></td>\
|
||||
</tr>`
|
||||
|
||||
}
|
||||
html += ' </tbody></table>'
|
||||
$('#list_playlist').html(html)
|
||||
|
||||
|
||||
for (var i=0;i<dataJSON.length;i++) {
|
||||
var id_playlist = dataJSON[i].id_playlist
|
||||
var element = $('#zone_delete_playlist_'+id_playlist)
|
||||
var state_playlist = dataJSON[i].activation
|
||||
$("#state_activation_"+id_playlist).val(state_playlist)
|
||||
|
||||
|
||||
element.click(function(){
|
||||
var idp = $(this).attr('id').replace("zone_delete_playlist_", "")
|
||||
console.log('delete playlist ' + idp)
|
||||
$('#id_playlist_to_action').val(idp)
|
||||
$('#modal_confirmation').modal('show')
|
||||
})
|
||||
|
||||
$('#btn_activation_playlist_'+id_playlist).click(function(){
|
||||
update_state_playlist(this)
|
||||
})
|
||||
}
|
||||
|
||||
function update_state_playlist(element) {
|
||||
var id = $(element).attr('id').replace("btn_activation_playlist_", "");
|
||||
console.log('change state from ' + $("#state_activation_"+id).val())
|
||||
if ($("#state_activation_"+id).val() == "1") {
|
||||
console.log('desactivate ' + id)
|
||||
$.get('activation_playlist', {activation:0, id_playlist:id}).done(function(data){
|
||||
var id_data = data['id_playlist']
|
||||
$("#state_activation_"+id).val(0)
|
||||
console.log(data)
|
||||
$("#btn_activation_playlist_"+id).addClass("btn-outline-secondary")
|
||||
$("#btn_activation_playlist_"+id).removeClass("btn-secondary")
|
||||
$("#btn_activation_playlist_"+id).html("Inactive")
|
||||
})
|
||||
|
||||
} else {
|
||||
console.log('activate ' + id)
|
||||
$.get('activation_playlist', {activation:1, id_playlist:id}).done(function(data){
|
||||
var id_data = data['id_playlist']
|
||||
console.log(data)
|
||||
$("#state_activation_"+id_data).val(1)
|
||||
$("#btn_activation_playlist_"+id_data).addClass("btn-secondary")
|
||||
$("#btn_activation_playlist_"+id_data).removeClass("btn-outline-secondary")
|
||||
$("#btn_activation_playlist_"+id_data).html("Active")
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// UPDATE LIST DEVICES
|
||||
function updateListDevices() {
|
||||
|
||||
$.get('list_devices').done(function (data) {
|
||||
console.log('Data received')
|
||||
var html = '<table class="table"> \
|
||||
<thead>\
|
||||
<tr>\
|
||||
<th scope="col">#</th>\
|
||||
<th scope="col">Device</th>\
|
||||
<th scope="col">LastDateTime</th>\
|
||||
<!--<th scope="col">Sid</th>-->\
|
||||
<th scope="col">Channel</th>\
|
||||
</tr>\
|
||||
</thead>\
|
||||
<tbody>'
|
||||
|
||||
var html2 = ''
|
||||
var html3 = ''
|
||||
|
||||
var dataJSON = JSON.parse(data)
|
||||
|
||||
for (var i=0;i<dataJSON.length;i++) {
|
||||
var sid_device = dataJSON[i].sid
|
||||
var timestamp = dataJSON[i].last_datetime
|
||||
var actualTS = Math.floor(Date.now() / 1000)
|
||||
var delta = actualTS - timestamp
|
||||
var status = ''
|
||||
//var media = '<div class="alert alert-primary" role="alert">' + dataJSON[i].mediaRunning + '</div>'
|
||||
var media = '<span class="badge bg-success">'+dataJSON[i].mediaRunning+'</span>'
|
||||
if (delta < 60) {
|
||||
status = '<span style="color: green;">Online</span>'
|
||||
status2 = '#ebfaeb'
|
||||
}
|
||||
if (delta > 86400) {
|
||||
status = '<span style="color: SlateBlue;">Offline</span>'
|
||||
status2 = '#e6e6ff'
|
||||
}
|
||||
if ((delta > 60) && (delta <= 86400)) {
|
||||
status = '<span style="color: red;">' + Math.floor(delta / 60).toString() + ' min</span>'
|
||||
status2 = '#ffe6e6'
|
||||
}
|
||||
|
||||
html += `<tr class="lineTable_device" data-name_device="${dataJSON[i].name}" data-sid_device="${sid_device}" >\
|
||||
<th scope="row">${i.toString()}</th>\
|
||||
<td>${dataJSON[i].name}</td>\
|
||||
<td>${status}</td>\
|
||||
<td>${media}</td>\
|
||||
<!--<td>'+dataJSON[i].sid+'</td>-->\
|
||||
</tr>`
|
||||
|
||||
html2 += '<li id="btn_select_device_'+dataJSON[i].id_device+'" data-param1="'+dataJSON[i].id_device+'" data-param2="'+dataJSON[i].name+'" data-param3="'+dataJSON[i].sid+'"><a class="dropdown-item" href="#">'+dataJSON[i].name+'</a></li>'
|
||||
|
||||
html3 += '<div id="btn_select_device_card_'+dataJSON[i].id_device+'" data-param1="'+dataJSON[i].id_device+'" data-param2="'+dataJSON[i].name+'" data-param3="'+dataJSON[i].sid+'" class="card card_device" style="background-color:'+status2+'"> \
|
||||
<div class="card-body"> \
|
||||
<p class="card-text"><div class="d-flex justify-content-between"> \
|
||||
<div>'+dataJSON[i].name+'</div> \
|
||||
<div><i class="fas fa-trash-alt"></i></div></div></p> \
|
||||
<p class="card-text">'+status+'</p> \
|
||||
<p class="card-text">'+media+'</p> \
|
||||
</div> \
|
||||
</div>'
|
||||
}
|
||||
html += ' </tbody></table>'
|
||||
$('#listDevices').html(html)
|
||||
//$('#list_device_home').html(html2)
|
||||
//$('#zone_device_main').html(html3)
|
||||
|
||||
function configure_current_device(element) {
|
||||
const id_device = $(element).data('param1');
|
||||
const name_device = $(element).data('param2');
|
||||
const sid = $(element).data('param3');
|
||||
console.log(id_device, name_device)
|
||||
$('#id_device').val(id_device)
|
||||
$('#sidDevice').val(sid)
|
||||
$('#btn_select_device').html(name_device)
|
||||
}
|
||||
|
||||
for (var i=0;i<dataJSON.length;i++) {
|
||||
$('#btn_select_device_'+dataJSON[i].id_device).click(function(){
|
||||
configure_current_device(this)
|
||||
})
|
||||
$("#btn_select_device_card_"+dataJSON[i].id_device).click(function(){
|
||||
configure_current_device(this)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
$(document).on("click", ".lineTable_device", function() {
|
||||
var sid = $(this).data('sid_device')
|
||||
var name_device = $(this).data('name_device')
|
||||
console.log("sid",sid)
|
||||
$('#NameDevice').html(name_device)
|
||||
$('#sidDevice').val(sid)
|
||||
getChannelRunning()
|
||||
channelInterval = setInterval(getChannelRunning, 5000)
|
||||
})
|
||||
|
||||
function getChannelRunning() {
|
||||
socket.emit('communication', {fromSidDevice : $('#sidClient').val(), toSidDevice: $('#sidDevice').val(), typeCommand : "getChannelRunning", payload: '', "packet":0});
|
||||
}
|
||||
|
||||
|
||||
// CLICK TAB HOME
|
||||
$('#display_tab_home').click(function(){
|
||||
var triggerTab = new bootstrap.Tab($('#display_tab_home'));
|
||||
triggerTab.show();
|
||||
$('#tab_home').show()
|
||||
$('#tab_search').hide()
|
||||
$('#tab_playlist').hide()
|
||||
})
|
||||
// CLICK TAB SEARCH
|
||||
$('#display_tab_search').click(function(){
|
||||
var triggerTab = new bootstrap.Tab($('#display_tab_search'));
|
||||
triggerTab.show();
|
||||
$('#tab_home').hide()
|
||||
$('#tab_search').show()
|
||||
$('#tab_playlist').hide()
|
||||
})
|
||||
|
||||
|
||||
// CLICK ADD PLAYLIST
|
||||
$('#btn_configure_playlist').click(function(){
|
||||
$('#modal_add_playlist').modal('show')
|
||||
})
|
||||
|
||||
// Check valid URL
|
||||
function isValidURL(string) {
|
||||
var pattern = new RegExp('^(https?:\\/\\/)?' + // Protocol
|
||||
'((([a-zA-Z0-9\\-]+\\.)+[a-zA-Z]{2,})|' + // Domain name
|
||||
'localhost|' + // OR localhost
|
||||
'\\d{1,3}(\\.\\d{1,3}){3})' + // OR IPv4
|
||||
'(\\:\\d+)?' + // Optional port
|
||||
'(\\/[-a-zA-Z0-9@:%._\\+~#=]*)*' + // Path
|
||||
'(\\?[;&a-zA-Z0-9@:%._\\+~#=]*)?' + // Query string
|
||||
'(\\#[-a-zA-Z0-9@:%._\\+~#=]*)?$', 'i'); // Fragment locator
|
||||
return pattern.test(string);
|
||||
}
|
||||
|
||||
// Update filemame
|
||||
$("#file-input").on("change", function () {
|
||||
$("#fileName").val(this.files[0]?.name || "No file selected.");
|
||||
});
|
||||
|
||||
|
||||
|
||||
// CLICK BTN btn_confirmation_delete_playlist
|
||||
$('#btn_confirmation_delete_playlist').click(function(){
|
||||
$.post('delete_playlist', {id_playlist : $('#id_playlist_to_action').val() }).done(function(data){
|
||||
console.log(data)
|
||||
update_list_playlist()
|
||||
$('#modal_confirmation').modal('hide')
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// CLICK UPLOAD FILE PLAYLIST
|
||||
$("#upload-button").click(function () {
|
||||
|
||||
// Validation form
|
||||
var validation_date = false
|
||||
if ($('#playlist_end_date').val()) validation_date = true; else validation_date = false;
|
||||
if (!validation_date) {
|
||||
$('#playlist_end_date').addClass('is-invalid');
|
||||
$('#playlist_end_date').removeClass('is-valid');
|
||||
} else {
|
||||
$('#playlist_end_date').addClass('is-valid');
|
||||
$('#playlist_end_date').removeClass('is-invalid');
|
||||
}
|
||||
|
||||
var validation_url = isValidURL($('#playlist_url').val())
|
||||
if (!validation_url) {
|
||||
$('#playlist_url').addClass('is-invalid');
|
||||
$('#playlist_url').removeClass('is-valid');
|
||||
} else {
|
||||
$('#playlist_url').addClass('is-valid');
|
||||
$('#playlist_url').removeClass('is-invalid');
|
||||
}
|
||||
|
||||
var validation_title = !($('#playlist_title').val().trim() === '')
|
||||
if (!validation_title) {
|
||||
$('#playlist_title').addClass('is-invalid');
|
||||
$('#playlist_title').removeClass('is-valid');
|
||||
} else {
|
||||
$('#playlist_title').addClass('is-valid');
|
||||
$('#playlist_title').removeClass('is-invalid');
|
||||
}
|
||||
|
||||
if ((validation_title) && (validation_date) && (validation_url)) {
|
||||
|
||||
const file = $("#file-input")[0].files[0];
|
||||
if (!file) {
|
||||
$('#add_playlist_alert').html('<div class="alert alert-danger" role="alert">Please select a file</div>')
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/upload", true);
|
||||
|
||||
// Update progress bar
|
||||
xhr.upload.onprogress = function (event) {
|
||||
if (event.lengthComputable) {
|
||||
const percentComplete = Math.round((event.loaded / event.total) * 100);
|
||||
$("#progress-bar").val(percentComplete);
|
||||
$("#progress-text").text(percentComplete + "%");
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onloadstart = function () {
|
||||
$("#progress-container").show();
|
||||
};
|
||||
|
||||
xhr.onload = function () {
|
||||
if (xhr.status === 200) {
|
||||
$('#add_playlist_alert').html('<div class="alert alert-success" role="alert">Upload successful!</div>')
|
||||
var response =JSON.parse(xhr.response)
|
||||
id_playlist = response['id_playlist']
|
||||
if (response.status == 'ok') {
|
||||
|
||||
$.post('analyse_m3u_file', {'id_playlist':id_playlist,
|
||||
'title':$('#playlist_title').val(),
|
||||
'url':$('#playlist_url').val(),
|
||||
'date_end':$('#playlist_end_date').val(),
|
||||
'filename':$("#fileName").val()}).done(function(data){
|
||||
|
||||
var num_channel = data.num_channel
|
||||
if (num_channel>0) {
|
||||
$('#add_playlist_alert').html('<div class="alert alert-success" role="alert">Num channel : '+String(num_channel)+'</div>')
|
||||
} else {
|
||||
$('#add_playlist_alert').html('<div class="alert alert-danger" role="alert">No channel in the file</div>')
|
||||
}
|
||||
update_list_playlist()
|
||||
$('#modal_add_playlist').modal('hide')
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
} else {
|
||||
$('#add_playlist_alert').html('<div class="alert alert-danger" role="alert">Upload failed!</div>')
|
||||
}
|
||||
$("#progress-container").hide();
|
||||
};
|
||||
|
||||
xhr.onerror = function () {
|
||||
alert("An error occurred while uploading.");
|
||||
$("#progress-container").hide();
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
$("#typeDisplay").change(function(){
|
||||
console.log($("#typeDisplay").val())
|
||||
displayResult()
|
||||
})
|
||||
|
||||
$('#btn_display_player').click(function () {
|
||||
if($('#player').css('display') == 'none'){
|
||||
$('#player').show('slow');
|
||||
$('#valRange').html='0'
|
||||
$('#Range').val(0)
|
||||
} else {
|
||||
$('#player').hide('slow');
|
||||
}
|
||||
})
|
||||
|
||||
$(".dropdown-submenu .dropdown-toggle").on("click", function (e) {
|
||||
e.preventDefault(); // Prevent link from navigating
|
||||
e.stopPropagation(); // Prevent closing the whole dropdown
|
||||
|
||||
let $submenu = $(this).next(".dropdown-menu");
|
||||
|
||||
// Toggle the submenu visibility
|
||||
$(".dropdown-submenu .dropdown-menu").not($submenu).removeClass("show"); // Close other submenus
|
||||
$submenu.toggleClass("show");
|
||||
});
|
||||
|
||||
// Close submenus when clicking outside
|
||||
$(document).on("click", function (e) {
|
||||
if (!$(e.target).closest(".dropdown-submenu").length) {
|
||||
$(".dropdown-submenu .dropdown-menu").removeClass("show");
|
||||
}
|
||||
});
|
||||
|
||||
function off(){
|
||||
console.log('OFF ',$('#sidDevice').val(), $('#sidClient').val())
|
||||
socket.emit('communication', {fromSidDevice : $('#sidClient').val(), toSidDevice: $('#sidDevice').val(), typeCommand : "off", payload: 0, "packet":0});
|
||||
}
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
+3255
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e # Stop script on error
|
||||
|
||||
IPTV_SOURCE_URL="${IPTV_SOURCE_URL:-https://iptv.mrk.ovh/get_source}"
|
||||
IPTV_DEST="/home/pi/Documents/IPTV.py"
|
||||
CONFIG_DB_SOURCE_URL="${CONFIG_DB_SOURCE_URL:-https://iptv.mrk.ovh/static/config_init.db}"
|
||||
CONFIG_DB_DEST="/home/pi/Documents/config.db"
|
||||
RC_LOCAL="/etc/rc.local"
|
||||
RC_LOCAL_CMD='sudo -u pi DISPLAY=:0 python3 /home/pi/Documents/IPTV.py &'
|
||||
CURL_CMD=(curl -fsSL)
|
||||
|
||||
if [ "${CURL_INSECURE:-0}" = "1" ]; then
|
||||
CURL_CMD+=( -k )
|
||||
fi
|
||||
|
||||
echo "Updating system"
|
||||
sudo apt update
|
||||
|
||||
echo "Installing packages"
|
||||
sudo apt -y install vim openvpn vsftpd curl ca-certificates python3-pil.imagetk python3-tk
|
||||
sudo update-ca-certificates
|
||||
|
||||
echo "Configuring vsftpd"
|
||||
VSFTPD_CONF="/etc/vsftpd.conf"
|
||||
|
||||
sudo sed -i 's/^#write_enable=YES/write_enable=YES/' $VSFTPD_CONF
|
||||
|
||||
echo "Restarting vsftpd service"
|
||||
sudo systemctl restart vsftpd
|
||||
|
||||
PYTHON_STDLIB_DIR="$(python3 -c 'import sysconfig; print(sysconfig.get_path("stdlib"))')"
|
||||
|
||||
echo "Fixing Python externally-managed restriction"
|
||||
sudo rm -f "$PYTHON_STDLIB_DIR/EXTERNALLY-MANAGED"
|
||||
|
||||
echo "Installing Python packages"
|
||||
sudo pip3 install python-vlc python-socketio wifi aiohttp websocket-client
|
||||
|
||||
echo "Downloading IPTV.py"
|
||||
sudo mkdir -p /home/pi/Documents
|
||||
sudo "${CURL_CMD[@]}" "$IPTV_SOURCE_URL" -o "$IPTV_DEST"
|
||||
sudo chown pi:pi "$IPTV_DEST"
|
||||
sudo chmod 755 "$IPTV_DEST"
|
||||
|
||||
echo "Downloading config database"
|
||||
sudo "${CURL_CMD[@]}" "$CONFIG_DB_SOURCE_URL" -o "$CONFIG_DB_DEST"
|
||||
sudo chown pi:pi "$CONFIG_DB_DEST"
|
||||
sudo chmod 644 "$CONFIG_DB_DEST"
|
||||
|
||||
echo "Configuring rc.local"
|
||||
if [ ! -f "$RC_LOCAL" ]; then
|
||||
sudo tee "$RC_LOCAL" >/dev/null <<'EOF'
|
||||
#!/bin/sh -e
|
||||
|
||||
exit 0
|
||||
EOF
|
||||
fi
|
||||
|
||||
if ! sudo grep -Fq "$RC_LOCAL_CMD" "$RC_LOCAL"; then
|
||||
sudo sed -i "\$i\\
|
||||
$RC_LOCAL_CMD
|
||||
" "$RC_LOCAL"
|
||||
fi
|
||||
|
||||
if ! sudo tail -n 1 "$RC_LOCAL" | grep -Fxq "exit 0"; then
|
||||
echo "rc.local must end with 'exit 0'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sudo chmod 755 "$RC_LOCAL"
|
||||
|
||||
echo "All tasks completed successfully!"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Reference in New Issue
Block a user