Updated kodi settings on Lenovo

This commit is contained in:
2026-03-22 22:28:43 +01:00
parent 725dfa7157
commit 32b5a81da6
10925 changed files with 575678 additions and 5511 deletions

View File

@@ -0,0 +1,254 @@
# Copyright (C) 2024 Jason Anderson, Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live 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.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class AVIChunk:
def __init__(self):
self.empty()
def empty(self):
self.size = 0
self.fourcc = ''
self.datatype = 1
self.chunk = ''
def read(self, thefile):
data = thefile.readBytes(4)
try: self.size = struct.unpack('<i', data)[0]
except: self.size = 0
# Putting an upper limit on the chunk size, in case the file is corrupt
if self.size > 0 and self.size < 10000:
self.chunk = thefile.readBytes(self.size)
else:
self.chunk = ''
self.size = 0
class AVIList:
def __init__(self):
self.empty()
def empty(self):
self.size = 0
self.fourcc = ''
self.datatype = 2
def read(self, thefile):
data = thefile.readBytes(4)
self.size = struct.unpack('<i', data)[0]
try: self.size = struct.unpack('<i', data)[0]
except: self.size = 0
self.fourcc = thefile.read(4)
class AVIHeader:
def __init__(self):
self.empty()
def empty(self):
self.dwMicroSecPerFrame = 0
self.dwMaxBytesPerSec = 0
self.dwPaddingGranularity = 0
self.dwFlags = 0
self.dwTotalFrames = 0
self.dwInitialFrames = 0
self.dwStreams = 0
self.dwSuggestedBufferSize = 0
self.dwWidth = 0
self.dwHeight = 0
class AVIStreamHeader:
def __init__(self):
self.empty()
def empty(self):
self.fccType = ''
self.fccHandler = ''
self.dwFlags = 0
self.wPriority = 0
self.wLanguage = 0
self.dwInitialFrame = 0
self.dwScale = 0
self.dwRate = 0
self.dwStart = 0
self.dwLength = 0
self.dwSuggestedBuffer = 0
self.dwQuality = 0
self.dwSampleSize = 0
self.rcFrame = ''
class AVIParser:
def __init__(self):
self.Header = AVIHeader()
self.StreamHeader = AVIStreamHeader()
def determineLength(self, filename: str) -> int and float:
log("AVIParser: determineLength %s"%filename)
try: self.File = FileAccess.open(filename, "rb", None)
except:
log("AVIParser: Unable to open the file")
return 0
dur = int(self.readHeader())
self.File.close()
log('AVIParser: Duration is %s'%(dur))
return dur
def readHeader(self):
# AVI Chunk
data = self.getChunkOrList()
if data.datatype != 2:
log("AVIParser: Not an avi")
return 0
#todo fix
if data.fourcc[0:4] != "AVI ":
log("AVIParser: Wrong FourCC")
return 0
# Header List
data = self.getChunkOrList()
if data.fourcc != "hdrl":
log("AVIParser: Header not found")
return 0
# Header chunk
data = self.getChunkOrList()
if data.fourcc != 'avih':
log('Header chunk not found')
return 0
self.parseHeader(data)
# Stream list
data = self.getChunkOrList()
if self.Header.dwStreams > 10:
self.Header.dwStreams = 10
for i in range(self.Header.dwStreams):
if data.datatype != 2:
log("AVIParser: Unable to find streams")
return 0
listsize = data.size
# Stream chunk number 1, the stream header
data = self.getChunkOrList()
if data.datatype != 1:
log("AVIParser: Broken stream header")
return 0
self.StreamHeader.empty()
self.parseStreamHeader(data)
# If this is the video header, determine the duration
if self.StreamHeader.fccType == 'vids':
return self.getStreamDuration()
# If this isn't the video header, skip through the rest of these
# stream chunks
try:
if listsize - data.size - 12 > 0:
self.File.seek(listsize - data.size - 12, 1)
data = self.getChunkOrList()
except:
log("AVIParser: Unable to seek")
log("AVIParser: Video stream not found")
return 0
def getStreamDuration(self):
try:
return int(self.StreamHeader.dwLength / (float(self.StreamHeader.dwRate) / float(self.StreamHeader.dwScale)))
except:
return 0
def parseHeader(self, data):
try:
header = struct.unpack('<iiiiiiiiiiiiii', data.chunk)
self.Header.dwMicroSecPerFrame = header[0]
self.Header.dwMaxBytesPerSec = header[1]
self.Header.dwPaddingGranularity = header[2]
self.Header.dwFlags = header[3]
self.Header.dwTotalFrames = header[4]
self.Header.dwInitialFrames = header[5]
self.Header.dwStreams = header[6]
self.Header.dwSuggestedBufferSize = header[7]
self.Header.dwWidth = header[8]
self.Header.dwHeight = header[9]
except:
self.Header.empty()
log("AVIParser: Unable to parse the header")
def parseStreamHeader(self, data):
try:
self.StreamHeader.fccType = data.chunk[0:4].decode('utf-8')
self.StreamHeader.fccHandler = data.chunk[4:8].decode('utf-8')
header = struct.unpack('<ihhiiiiiiiid', data.chunk[8:])
self.StreamHeader.dwFlags = header[0]
self.StreamHeader.wPriority = header[1]
self.StreamHeader.wLanguage = header[2]
self.StreamHeader.dwInitialFrame = header[3]
self.StreamHeader.dwScale = header[4]
self.StreamHeader.dwRate = header[5]
self.StreamHeader.dwStart = header[6]
self.StreamHeader.dwLength = header[7]
self.StreamHeader.dwSuggestedBuffer = header[8]
self.StreamHeader.dwQuality = header[9]
self.StreamHeader.dwSampleSize = header[10]
self.StreamHeader.rcFrame = ''
except:
self.StreamHeader.empty()
log("AVIParser: Error reading stream header")
def getChunkOrList(self):
try: data = self.File.readBytes(4).decode('utf-8')
except: data = self.File.read(4)
if data == "RIFF" or data == "LIST":
dataclass = AVIList()
elif len(data) == 0:
dataclass = AVIChunk()
dataclass.datatype = 3
else:
dataclass = AVIChunk()
dataclass.fourcc = data
# Fill in the chunk or list info
dataclass.read(self.File)
return dataclass

View File

@@ -0,0 +1,31 @@
# Copyright (C) 2024 Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live 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.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class FFProbe:
def determineLength(self, filename: str) -> int and float :
try:
import ffmpeg
log("FFProbe: determineLength %s"%(filename))
dur = ffmpeg.probe(FileAccess.translatePath(filename))["format"]["duration"]
log('FFProbe: Duration is %s'%(dur))
return dur
except Exception as e:
log("FFProbe: failed! %s"%(e), xbmc.LOGERROR)
return 0

View File

@@ -0,0 +1,144 @@
# Copyright (C) 2024 Jason Anderson, Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live 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.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class FLVTagHeader:
def __init__(self):
self.tagtype = 0
self.datasize = 0
self.timestamp = 0
self.timestampext = 0
def readHeader(self, thefile):
try:
data = struct.unpack('B', thefile.readBytes(1))[0]
self.tagtype = (data & 0x1F)
self.datasize = struct.unpack('>H', thefile.readBytes(2))[0]
data = struct.unpack('>B', thefile.readBytes(1))[0]
self.datasize = (self.datasize << 8) | data
self.timestamp = struct.unpack('>H', thefile.readBytes(2))[0]
data = struct.unpack('>B', thefile.readBytes(1))[0]
self.timestamp = (self.timestamp << 8) | data
self.timestampext = struct.unpack('>B', thefile.readBytes(1))[0]
except:
self.tagtype = 0
self.datasize = 0
self.timestamp = 0
self.timestampext = 0
class FLVParser:
def __init__(self):
self.monitor = MONITOR()
def determineLength(self, filename: str) -> int and float:
log("FLVParser: determineLength %s"%filename)
try: self.File = FileAccess.open(filename, "rb", None)
except:
log("FLVParser: Unable to open the file")
return 0
if self.verifyFLV() == False:
log("FLVParser: Not a valid FLV")
self.File.close()
return 0
tagheader = self.findLastVideoTag()
if tagheader is None:
log("FLVParser: Unable to find a video tag")
self.File.close()
return 0
dur = int(self.getDurFromTag(tagheader))
self.File.close()
log("FLVParser: Duration is %s"%(dur))
return dur
def verifyFLV(self):
data = self.File.read(3)
if data != 'FLV':
return False
return True
def findLastVideoTag(self):
try:
self.File.seek(0, 2)
curloc = self.File.tell()
except:
log("FLVParser: Exception seeking in findLastVideoTag")
return None
# Go through a limited amount of the file before quiting
maximum = curloc - (2 * 1024 * 1024)
if maximum < 0:
maximum = 8
while not self.monitor.abortRequested() and curloc > maximum:
try:
self.File.seek(-4, 1)
data = int(struct.unpack('>I', self.File.readBytes(4))[0])
if data < 1:
log('FLVParser: Invalid packet data')
return None
if curloc - data <= 0:
log('FLVParser: No video packet found')
return None
self.File.seek(-4 - data, 1)
curloc = curloc - data
tag = FLVTagHeader()
tag.readHeader(self.File)
if tag.datasize <= 0:
log('FLVParser: Invalid packet header')
return None
if curloc - 8 <= 0:
log('FLVParser: No video packet found')
return None
self.File.seek(-8, 1)
log("FLVParser: detected tag type %s"%(tag.tagtype))
curloc = self.File.tell()
if tag.tagtype == 9:
return tag
except:
log('FLVParser: Exception in findLastVideoTag')
return None
return None
def getDurFromTag(self, tag):
tottime = tag.timestamp | (tag.timestampext << 24)
tottime = int(tottime / 1000)
return tottime

View File

@@ -0,0 +1,35 @@
# Copyright (C) 2024 Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live 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.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class Hachoir:
def determineLength(self, filename: str) -> int and float:
try:
meta = {}
from hachoir.parser import createParser
from hachoir.metadata import extractMetadata
log("Hachoir: determineLength %s"%(filename))
meta = extractMetadata(createParser(FileAccess.open(filename,'r')))
if not meta: raise Exception('No meta found')
dur = meta.get('duration').total_seconds()
log('Hachoir: Duration is %s'%(dur))
return dur
except Exception as e:
log("Hachoir: failed! %s\nmeta = %s"%(e,meta), xbmc.LOGERROR)
return 0

View File

@@ -0,0 +1,210 @@
# Copyright (C) 2024Jason Anderson, Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live 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.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class MKVParser:
monitor = xbmc.Monitor()
def determineLength(self, filename: str) -> int and float:
log("MKVParser: determineLength %s"%filename)
try: self.File = FileAccess.open(filename, "rb", None)
except:
log("MKVParser: Unable to open the file")
log(traceback.format_exc(), xbmc.LOGERROR)
return
size = self.findHeader()
if size == 0:
log('MKVParser: Unable to find the segment info')
dur = 0
else:
dur = int(round(self.parseHeader(size)))
log("MKVParser: Duration is %s"%(dur))
return dur
def parseHeader(self, size):
duration = 0
timecode = 0
fileend = self.File.tell() + size
datasize = 1
data = 1
while not self.monitor.abortRequested() and self.File.tell() < fileend and datasize > 0 and data > 0:
data = self.getEBMLId()
datasize = self.getDataSize()
if data == 0x2ad7b1:
timecode = 0
try:
for x in range(datasize):
timecode = (timecode << 8) + struct.unpack('B', self.getData(1))[0]
except:
timecode = 0
if duration != 0 and timecode != 0:
break
elif data == 0x4489:
try:
if datasize == 4:
duration = int(struct.unpack('>f', self.getData(datasize))[0])
else:
duration = int(struct.unpack('>d', self.getData(datasize))[0])
except:
log("MKVParser: Error getting duration in header, size is " + str(datasize))
duration = 0
if timecode != 0 and duration != 0:
break
else:
try:
self.File.seek(datasize, 1)
except:
log('MKVParser: Error while seeking')
return 0
if duration > 0 and timecode > 0:
dur = (duration * timecode) / 1000000000
return dur
return 0
def findHeader(self):
log("MKVParser: findHeader")
filesize = self.getFileSize()
if filesize == 0:
log("MKVParser: Empty file")
return 0
data = self.getEBMLId()
# Check for 1A 45 DF A3
if data != 0x1A45DFA3:
log("MKVParser: Not a proper MKV")
return 0
datasize = self.getDataSize()
try:
self.File.seek(datasize, 1)
except:
log('MKVParser: Error while seeking')
return 0
data = self.getEBMLId()
# Look for the segment header
while not self.monitor.abortRequested() and data != 0x18538067 and self.File.tell() < filesize and data > 0 and datasize > 0:
datasize = self.getDataSize()
try:
self.File.seek(datasize, 1)
except:
log('MKVParser: Error while seeking')
return 0
data = self.getEBMLId()
datasize = self.getDataSize()
data = self.getEBMLId()
# Find segment info
while not self.monitor.abortRequested() and data != 0x1549A966 and self.File.tell() < filesize and data > 0 and datasize > 0:
datasize = self.getDataSize()
try:
self.File.seek(datasize, 1)
except:
log('MKVParser: Error while seeking')
return 0
data = self.getEBMLId()
datasize = self.getDataSize()
if self.File.tell() < filesize:
return datasize
return 0
def getFileSize(self):
size = 0
try:
pos = self.File.tell()
self.File.seek(0, 2)
size = self.File.tell()
self.File.seek(pos, 0)
except:
pass
return size
def getData(self, datasize):
data = self.File.readBytes(datasize)
return data
def getDataSize(self):
data = self.File.readBytes(1)
try:
firstbyte = struct.unpack('>B', data)[0]
datasize = firstbyte
mask = 0xFFFF
for i in range(8):
if datasize >> (7 - i) == 1:
mask = mask ^ (1 << (7 - i))
break
datasize = datasize & mask
if firstbyte >> 7 != 1:
for i in range(1, 8):
datasize = (datasize << 8) + struct.unpack('>B', self.File.readBytes(1))[0]
if firstbyte >> (7 - i) == 1:
break
except:
datasize = 0
return datasize
def getEBMLId(self):
data = self.File.readBytes(1)
try:
firstbyte = struct.unpack('>B', data)[0]
ID = firstbyte
if firstbyte >> 7 != 1:
for i in range(1, 4):
ID = (ID << 8) + struct.unpack('>B', self.File.readBytes(1))[0]
if firstbyte >> (7 - i) == 1:
break
except:
ID = 0
return ID

View File

@@ -0,0 +1,185 @@
# Copyright (C) 2024 Jason Anderson, Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live 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.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class MP4DataBlock:
def __init__(self):
self.size = -1
self.boxtype = ''
self.data = ''
class MP4MovieHeader:
def __init__(self):
self.version = 0
self.flags = 0
self.created = 0
self.modified = 0
self.scale = 0
self.duration = 0
class MP4Parser:
def __init__(self):
self.MovieHeader = MP4MovieHeader()
self.monitor = MONITOR()
def determineLength(self, filename: str) -> int and float:
log("MP4Parser: determineLength %s"%filename)
try: self.File = FileAccess.open(filename, "rb", None)
except:
log("MP4Parser: Unable to open the file")
return 0
dur = self.readHeader()
if not dur:
log("MP4Parser - Using New Parser")
boxes = self.find_boxes(self.File)
# Sanity check that this really is a movie file.
if (boxes.get(b"ftyp",[-1])[0] == 0):
try:
moov_boxes = self.find_boxes(self.File, boxes[b"moov"][0] + 8, boxes[b"moov"][1])
trak_boxes = self.find_boxes(self.File, moov_boxes[b"trak"][0] + 8, moov_boxes[b"trak"][1])
udta_boxes = self.find_boxes(self.File, moov_boxes[b"udta"][0] + 8, moov_boxes[b"udta"][1])
dur = self.scan_mvhd(self.File, moov_boxes[b"mvhd"][0])
except Exception as e:
log("MP4Parser, failed! %s\nboxes = %s"%(e,boxes), xbmc.LOGERROR)
dur = 0
self.File.close()
log("MP4Parser: Duration is %s"%(dur))
return dur
def find_boxes(self, f, start_offset=0, end_offset=float("inf")):
"""Returns a dictionary of all the data boxes and their absolute starting
and ending offsets inside the mp4 file. Specify a start_offset and end_offset to read sub-boxes."""
s = struct.Struct("> I 4s")
boxes = {}
offset = start_offset
last_offset = -1
f.seek(offset, 0)
while not self.monitor.abortRequested() and offset < end_offset:
try:
if last_offset == offset: break
else: last_offset = offset
data = f.readBytes(8) # read box header
if data == b"": break # EOF
length, text = s.unpack(data)
f.seek(length - 8, 1) # skip to next box
boxes[text] = (offset, offset + length)
offset += length
except: pass
return boxes
def scan_mvhd(self, f, offset):
f.seek(offset, 0)
f.seek(8, 1) # skip box header
data = f.readBytes(1) # read version number
version = int.from_bytes(data, "big")
word_size = 8 if version == 1 else 4
f.seek(3, 1) # skip flags
f.seek(word_size * 2, 1) # skip dates
timescale = int.from_bytes(f.readBytes(4), "big")
if timescale == 0: timescale = 600
duration = int.from_bytes(f.readBytes(word_size), "big")
duration = round(duration / timescale)
return duration
def readHeader(self):
data = self.readBlock()
if data.boxtype != 'ftyp':
log("MP4Parser: No file block")
return 0
# Skip past the file header
try:
self.File.seek(data.size, 1)
except:
log('MP4Parser: Error while seeking')
return 0
data = self.readBlock()
while not self.monitor.abortRequested() and data.boxtype != 'moov' and data.size > 0:
try: self.File.seek(data.size, 1)
except:
log('MP4Parser: Error while seeking')
return 0
data = self.readBlock()
data = self.readBlock()
while not self.monitor.abortRequested() and data.boxtype != 'mvhd' and data.size > 0:
try: self.File.seek(data.size, 1)
except:
log('MP4Parser: Error while seeking')
return 0
data = self.readBlock()
self.readMovieHeader()
if self.MovieHeader.scale > 0 and self.MovieHeader.duration > 0:
return int(self.MovieHeader.duration / self.MovieHeader.scale)
return 0
def readMovieHeader(self):
try:
self.MovieHeader.version = struct.unpack('>b', self.File.readBytes(1))[0]
self.File.read(3) #skip flags for now
if self.MovieHeader.version == 1:
data = struct.unpack('>QQIQQ', self.File.readBytes(36))
else:
data = struct.unpack('>IIIII', self.File.readBytes(20))
self.MovieHeader.created = data[0]
self.MovieHeader.modified = data[1]
self.MovieHeader.scale = data[2]
self.MovieHeader.duration = data[3]
except:
self.MovieHeader.duration = 0
def readBlock(self):
box = MP4DataBlock()
try:
data = self.File.readBytes(4)
box.size = struct.unpack('>I', data)[0]
box.boxtype = self.File.read(4)
if box.size == 1:
box.size = struct.unpack('>q', self.File.readBytes(8))[0]
box.size -= 8
box.size -= 8
if box.boxtype == 'uuid':
box.boxtype = self.File.read(16)
box.size -= 16
except:
pass
return box

View File

@@ -0,0 +1,50 @@
# Copyright (C) 2024 Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live 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.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class MediaInfo:
def determineLength(self, filename: str) -> int and float:
try:
from pymediainfo import MediaInfo
dur = 0
mi = None
fileXML = filename.replace('.%s'%(filename.rsplit('.',1)[1]),'-mediainfo.xml')
if FileAccess.exists(fileXML):
log("MediaInfo: parsing XML %s"%(fileXML))
fle = FileAccess.open(fileXML, 'rb')
mi = MediaInfo(fle.read())
fle.close()
else:
log("MediaInfo: parsing %s"%(FileAccess.translatePath(filename)))
mi = MediaInfo.parse(FileAccess.translatePath(filename))
if not mi is None and mi.tracks:
for track in mi.tracks:
if track.track_type == 'General':
dur = track.duration / 1000
break
log("MediaInfo: determineLength %s Duration is %s"%(filename,dur))
return dur
except Exception as e:
log("MediaInfo: failed! %s"%(e), xbmc.LOGERROR)
return 0

View File

@@ -0,0 +1,31 @@
# Copyright (C) 2024 Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live 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.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class MoviePY:
def determineLength(self, filename: str) -> int and float:
try:
from moviepy.editor import VideoFileClip
log("MoviePY: determineLength %s"%(filename))
dur = VideoFileClip(FileAccess.translatePath(filename)).duration
log('MoviePY: Duration is %s'%(dur))
return dur
except Exception as e:
log("MoviePY: failed! %s"%(e), xbmc.LOGERROR)
return 0

View File

@@ -0,0 +1,73 @@
# Copyright (C) 2024 Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live Live 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.
#
# PseudoTV Live Live 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 PseudoTV Live Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class NFOParser:
## NFO EXAMPLE ##
##<episodedetails>
## <runtime>25</runtime>
## <duration>1575</duration>
## <fileinfo>
## <streamdetails>
## <video>
## <durationinseconds>1575</durationinseconds>
## </video>
## </streamdetails>
## </fileinfo>
##</episodedetails>
def determineLength(self, filename: str) -> int and float:
duration = 0
fleName, fleExt = os.path.splitext(filename)
fleName += '.nfo'
if not FileAccess.exists(fleName):
log("NFOParser: Unable to locate NFO %s"%(fleName), xbmc.LOGERROR)
return 0
log("NFOParser: determineLength, file = %s, nfo = %s"%(filename,fleName))
try:
File = FileAccess.open(fleName, "rb")
dom = parse(File)
File.close()
except:
log("NFOParser: Unable to open the file %s"%(fleName), xbmc.LOGERROR)
return duration
try:
xmldurationinseconds = dom.getElementsByTagName('durationinseconds')[0].toxml()
duration = int(xmldurationinseconds.replace('<durationinseconds>','').replace('</durationinseconds>',''))
except Exception as e:
log("NFOParser: <durationinseconds> not found")
if duration == 0:
try:
xmlruntime = dom.getElementsByTagName('runtime')[0].toxml()
duration = int(xmlruntime.replace('<runtime>','').replace('</runtime>','').replace(' min.','')) * 60
except Exception as e:
log("NFOParser: <runtime> not found")
if duration == 0:
try:
xmlruntime = dom.getElementsByTagName('duration')[0].toxml()
duration = int(xmlruntime.replace('<duration>','').replace('</duration>','')) * 60
except Exception as e:
log("NFOParser: <duration> not found")
log("NFOParser: Duration is %s"%(duration))
return duration

View File

@@ -0,0 +1,31 @@
# Copyright (C) 2024 Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live 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.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class OpenCV:
def determineLength(self, filename: str) -> int and float:
try:
import cv2
log("OpenCV: determineLength %s"%(filename))
dur = cv2.VideoCapture(FileAccess.translatePath(filename)).get(cv2.CAP_PROP_POS_MSEC)
log('OpenCV: Duration is %s'%(dur))
return dur
except Exception as e:
log("OpenCV: failed! %s"%(e), xbmc.LOGERROR)
return 0

View File

@@ -0,0 +1,247 @@
# Copyright (C) 2024 Jason Anderson, Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live 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.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class TSPacket:
def __init__(self):
self.pid = 0
self.errorbit = 1
self.pesstartbit = 0
self.adaption = 1
self.adaptiondata = ''
self.pesdata = ''
class TSParser:
def __init__(self):
self.monitor = MONITOR()
def determineLength(self, filename: str) -> int and float:
log("TSParser: determineLength %s"%filename)
self.pid = -1
try: self.File = FileAccess.open(filename, "rb", None)
except:
log("TSParser: Unable to open the file")
return 0
self.filesize = self.getFileSize()
self.packetLength = self.findPacketLength()
if self.packetLength <= 0:
return 0
start = self.getStartTime()
log('TSParser: Start %s'%(start))
end = self.getEndTime()
log('TSParser: End - %s'%(end))
if end > start:
dur = int((end - start) / 90000)
else:
dur = 0
self.File.close()
log("TSParser: Duration is %s"%(dur))
return dur
def findPacketLength(self):
log('TSParser: findPacketLength')
maxbytes = 600
start = 0
self.packetLength = 0
while not self.monitor.abortRequested() and maxbytes > 0:
maxbytes -= 1
try:
data = self.File.readBytes(1)
data = struct.unpack('B', data)
if data[0] == 71:
if start > 0:
end = self.File.tell()
break
else:
start = self.File.tell()
# A minimum of 188, so skip the rest
self.File.seek(187, 1)
except:
log('TSParser: Exception in findPacketLength')
return
if (start > 0) and (end > start):
log('TSParser: Packet Length: %s'%(end - start))
return (end - start)
return
def getFileSize(self):
size = 0
try:
pos = self.File.tell()
self.File.seek(0, 2)
size = self.File.tell()
self.File.seek(pos, 0)
except:
pass
return size
def getStartTime(self):
# A reasonably high number of retries in case the PES starts in the middle
# and is it's maximum length
maxpackets = 12000
log('TSParser: getStartTime')
try:
self.File.seek(0, 0)
except:
return 0
while not self.monitor.abortRequested() and maxpackets > 0:
packet = self.readTSPacket()
maxpackets -= 1
if packet == None:
return 0
if packet.errorbit == 0 and packet.pesstartbit == 1:
ret = self.getPTS(packet)
if ret > 0:
self.pid = packet.pid
log('TSParser: PID: %s'%(self.pid))
return ret
return 0
def getEndTime(self):
log('TSParser: getEndTime')
packetcount = int(self.filesize / self.packetLength)
try:
self.File.seek((packetcount * self.packetLength)- self.packetLength, 0)
except:
return 0
maxpackets = 12000
while not self.monitor.abortRequested() and maxpackets > 0:
packet = self.readTSPacket()
maxpackets -= 1
if packet == None:
log('TSParser: getEndTime got a null packet')
return 0
if packet.errorbit == 0 and packet.pesstartbit == 1 and packet.pid == self.pid:
ret = self.getPTS(packet)
if ret > 0:
log('TSParser: getEndTime returning time')
return ret
else:
try:
self.File.seek(-1 * (self.packetLength * 2), 1)
except:
log('TSParser: exception')
return 0
log('TSParser: getEndTime no found end time')
return 0
def getPTS(self, packet):
timestamp = 0
log('TSParser: getPTS')
try:
data = struct.unpack('19B', packet.pesdata[:19])
# start code
if data[0] == 0 and data[1] == 0 and data[2] == 1:
# cant be a navigation packet
if data[3] != 190 and data[3] != 191:
offset = 0
if (data[9] >> 4) == 3:
offset = 5
# a little dangerous...ignoring the LSB of the timestamp
timestamp = ((data[9 + offset] >> 1) & 7) << 30
timestamp = timestamp | (data[10 + offset] << 22)
timestamp = timestamp | ((data[11 + offset] >> 1) << 15)
timestamp = timestamp | (data[12 + offset] << 7)
timestamp = timestamp | (data[13 + offset] >> 1)
return timestamp
except:
log('TSParser: exception in getPTS')
pass
log('TSParser: getPTS returning 0')
return 0
def readTSPacket(self):
packet = TSPacket()
pos = 0
try:
data = self.File.readBytes(4)
pos = 4
data = struct.unpack('4B', data)
if data[0] == 71:
packet.pid = (data[1] & 31) << 8
packet.pid = packet.pid | data[2]
# skip tables and null packets
if packet.pid < 21 or packet.pid == 8191:
self.File.seek(self.packetLength - 4, 1)
else:
packet.adaption = (data[3] >> 4) & 3
packet.errorbit = data[1] >> 7
packet.pesstartbit = (data[1] >> 6) & 1
if packet.adaption > 1:
data = self.File.readBytes(1)
length = struct.unpack('B', data)[0]
if length > 0:
data = self.File.readBytes(length)
else:
length = 0
pos += length + 1
if pos < 188:
# read the PES data
packet.pesdata = self.File.readBytes(self.packetLength - pos)
except:
log('TSParser: readTSPacket exception')
return None
return packet

View File

@@ -0,0 +1,29 @@
# Copyright (C) 2024 Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live Live 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.
#
# PseudoTV Live Live 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 PseudoTV Live Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class VFSParser:
def determineLength(self, filename: str, fileitem: dict={}, jsonRPC=None)-> int and float:
log("VFSParser: determineLength, file = %s\nitem = %s"%(filename,fileitem))
duration = (fileitem.get('resume',{}).get('total') or fileitem.get('runtime') or fileitem.get('duration') or (fileitem.get('streamdetails',{}).get('video',[]) or [{}])[0].get('duration') or 0)
if duration == 0 and not filename.lower().startswith(fileitem.get('originalpath','').lower()) and not filename.lower().startswith(tuple(self.VFSPaths)):
metadata = self.jsonRPC.getFileDetails((fileitem.get('originalpath') or fileitem.get('file') or filename))
duration = (metadata.get('resume',{}).get('total') or metadata.get('runtime') or metadata.get('duration') or (metadata.get('streamdetails',{}).get('video',[]) or [{}])[0].get('duration') or 0)
log("VFSParser: Duration is %s"%(duration))
return duration

View File

@@ -0,0 +1,38 @@
# Copyright (C) 2024 Lunatixz
#
#
# This file is part of PseudoTV Live.
#
# PseudoTV Live 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.
#
# PseudoTV Live 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 PseudoTV Live. If not, see <http://www.gnu.org/licenses/>.
from globals import *
class YTParser:
def determineLength(self, filename: str) -> int and float:
try:
dur = 0
if hasAddon('script.module.youtube.dl'):
from youtube_dl import YoutubeDL
if 'videoid' in filename: vID = (re.compile(r'videoid\=(.*)' , re.IGNORECASE).search(filename)).group(1)
elif 'video_id' in filename: vID = (re.compile(r'video_id\=(.*)', re.IGNORECASE).search(filename)).group(1)
else: raise Exception('No video_id found!')
log("YTParser: determineLength, file = %s, id = %s"%(filename,vID))
ydl = YoutubeDL({'no_color': True, 'format': 'best', 'outtmpl': '%(id)s.%(ext)s', 'no-mtime': True, 'add-header': HEADER})
with ydl:
dur = ydl.extract_info("https://www.youtube.com/watch?v={sID}".format(sID=vID), download=False).get('duration',0)
log('YTParser: Duration is %s'%(dur))
return dur
except Exception as e:
log("YTParser: failed! %s\nfile = %s"%(e,filename), xbmc.LOGERROR)
return 0