Updated kodi settings on Lenovo
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user