Compare commits

...

2 Commits

Author SHA1 Message Date
Frédéric Tronel
926ee16433 Improve pylint score and fix most errors. 2025-10-25 16:09:11 +02:00
Frédéric Tronel
489435a87f Improve pylint score and fix most errors. 2025-10-25 16:05:25 +02:00

View File

@@ -1,9 +1,10 @@
#!/usr/bin/env python3
'''A module to remove parts of video (.e.g advertisements) with single frame precision.'''
import argparse
import re
from sys import exit
from datetime import datetime,timedelta,time
from datetime import datetime,timedelta
import coloredlogs, logging
from functools import cmp_to_key
from subprocess import Popen, PIPE
@@ -35,7 +36,7 @@ from dataclasses import dataclass, field
# Concatenate all raw H264 in a giant one (like cat), and the same for timestamps of video frames (to keep
# sound and video synchronized).
# Then use mkvmerge to remux the H264 track and the rest of tracks.
# MKVmerge concatenate is able to concatenate different SPS/PPS data into a bigger Private Codec Data.
# MKVmerge "concatenate" subcommand is able to concatenate different SPS/PPS data into a bigger Private Codec Data.
# However, this is proved to be not reliable. Sometimes it results in a AVC context containing a single SPS/PPS.
# So we have to rely on a manual parsing of the H264 AVC context of original movie
# and the ones produced for headers and trailers, and then merging them into a bigger AVC context.
@@ -50,15 +51,15 @@ def checkRequiredTools():
optional = ['mkvextract', 'vobsubocr','tesseract']
for tool in required:
path = which(tool)
if path == None:
logger.error('Required tool: %s is missing.' % tool)
if path is None:
logger.error('Required tool: %s is missing.',tool)
exit(-1)
else:
paths[tool] = path
paths[tool] = path
for tool in optional:
path = which(tool)
if path == None:
logger.info('Optional tool: %s is missing.' % tool)
if path is None:
logger.info('Optional tool: %s is missing.',tool)
allOptionalTools = False
else:
paths[tool] = path
@@ -74,7 +75,7 @@ def getTesseractSupportedLang(tesseract):
line = line.decode('utf8')
p = re.compile('(?P<lang>[a-z]{3})\n')
m = re.match(p,line)
if m != None:
if m is not None:
try:
lang = m.group('lang')
key = Lang(lang)
@@ -83,9 +84,9 @@ def getTesseractSupportedLang(tesseract):
pass
tesseract.wait()
if tesseract.returncode != 0:
logger.error("Tesseract returns an error code: %d" % tesseract.returncode)
logger.error("Tesseract returns an error code: %d",tesseract.returncode)
return None
return res
@@ -105,7 +106,8 @@ def getFrameRate(ffprobe, inputFile):
maxTs = None
interlaced = False
params = [ffprobe, '-loglevel', 'quiet', '-select_streams', 'v', '-show_frames', '-read_intervals', '00%+30', '-of', 'json', '/proc/self/fd/%d' % infd]
params = [ffprobe, '-loglevel', 'quiet', '-select_streams', 'v', '-show_frames',
'-read_intervals', '00%+30', '-of', 'json', '/proc/self/fd/%d' % infd]
env = {**os.environ, 'LANG': 'C'}
with Popen(params, stdout=PIPE, close_fds=False, env=env) as ffprobe:
out, _ = ffprobe.communicate()
@@ -117,9 +119,9 @@ def getFrameRate(ffprobe, inputFile):
interlaced = True
if 'pts_time' in frame:
ts = float(frame['pts_time'])
if minTs == None:
if minTs is None:
minTs = ts
if maxTs == None:
if maxTs is None:
maxTs = ts
if ts < minTs:
minTs = ts
@@ -135,7 +137,7 @@ def getFrameRate(ffprobe, inputFile):
ffprobe.wait()
if ffprobe.returncode != 0:
logger.error("ffprobe returns an error code: %d" % ffprobe.returncode)
logger.error("ffprobe returns an error code: %d", ffprobe.returncode)
return None
frameRate1 = nbFrames1/(maxTs-minTs)
@@ -143,12 +145,12 @@ def getFrameRate(ffprobe, inputFile):
if abs(frameRate1 - frameRate2) > 0.2:
if not interlaced:
logger.error('Video is not interlaced and the disperancy between frame rates is too big: %f / %f' % (frameRate1, frameRate2))
logger.error('Video is not interlaced and the disperancy between frame rates is too big: %f / %f', frameRate1, frameRate2)
return None
if abs(frameRate1*2 - frameRate2) < 0.2:
return frameRate2/2
else:
logger.error('Video is interlaced and the disperancy between frame rates is too big: %f / %f' % (frameRate1, frameRate2))
logger.error('Video is interlaced and the disperancy between frame rates is too big: %f / %f', frameRate1, frameRate2)
return None
else:
return frameRate2
@@ -160,7 +162,8 @@ def getSubTitlesTracks(ffprobe, mkvPath):
tracks={}
nbSubTitles = 0
with Popen([ffprobe, '-loglevel', 'quiet', '-select_streams', 's', '-show_entries', 'stream=index,codec_name:stream_tags=language', '-of', 'json', mkvPath], stdout=PIPE) as ffprobe:
with Popen([ffprobe, '-loglevel', 'quiet', '-select_streams', 's', '-show_entries',
'stream=index,codec_name:stream_tags=language', '-of', 'json', mkvPath], stdout=PIPE) as ffprobe:
out, _ = ffprobe.communicate()
out = json.load(BytesIO(out))
if 'streams' in out:
@@ -170,10 +173,10 @@ def getSubTitlesTracks(ffprobe, mkvPath):
lang = stream['tags']['language']
if codec == 'dvd_subtitle':
if lang not in tracks:
tracks[lang] = [track]
tracks[lang] = [index]
else:
l = tracks[lang]
l.append(track)
l.append(index)
tracks[lang] = l
else:
return None
@@ -181,7 +184,7 @@ def getSubTitlesTracks(ffprobe, mkvPath):
ffprobe.wait()
if ffprobe.returncode != 0:
logger.error("ffprobe returns an error code: %d" % ffprobe.returncode)
logger.error("ffprobe returns an error code: %d", ffprobe.returncode)
return None
return tracks
@@ -199,7 +202,7 @@ def extractSRT(mkvextract, fileName, subtitles, langs):
if iso in langs:
ocrlang = langs[iso]
else:
logger.warning("Language not supported by Tesseract: %s" % iso.name)
logger.warning("Language not supported by Tesseract: %s", iso.name)
ocrlang ='osd'
if len(subtitles[lang]) == 1:
@@ -221,7 +224,7 @@ def extractSRT(mkvextract, fileName, subtitles, langs):
if line.startswith('Progress :'):
p = re.compile('^Progress : (?P<progress>[0-9]{1,3})%$')
m = p.match(line)
if m == None:
if m is None:
logger.error('Impossible to parse progress')
pb.update(int(m['progress'])-pb.n)
pb.update(100-pb.n)
@@ -238,7 +241,7 @@ def extractSRT(mkvextract, fileName, subtitles, langs):
logger.warning('Mkvextract returns warning')
return res
else:
logger.error('Mkvextract returns an error code: %d' % extract.returncode)
logger.error('Mkvextract returns an error code: %d', extract.returncode)
return None
def doOCR(vobsubocr, idxs, duration, temporaries, dumpMemFD=False):
@@ -256,7 +259,7 @@ def doOCR(vobsubocr, idxs, duration, temporaries, dumpMemFD=False):
pb = tqdm(TextIOWrapper(ocr.stdout, encoding="utf-8"), total=int(duration/timedelta(seconds=1)), unit='s', desc='OCR')
for line in pb:
m = re.match(ldots,line)
if m != None:
if m is not None:
write(srtfd, '...'.encode(encoding='UTF-8'))
else:
write(srtfd, line.encode(encoding='UTF-8'))
@@ -273,13 +276,13 @@ def doOCR(vobsubocr, idxs, duration, temporaries, dumpMemFD=False):
status = ocr.wait()
if status != 0:
logger.error('OCR failed with status code: %d' % status)
logger.error('OCR failed with status code: %d', status)
if dumpMemFD:
try:
dumpSrt = open(srtname,'w')
except IOError:
logger.error('Impossible to create file: %s' % srtname)
logger.error('Impossible to create file: %s', srtname)
return None
lseek(srtfd, 0, SEEK_SET)
@@ -339,10 +342,10 @@ def getCodecPrivateDataFromMKV(mkvinfo, inputFile):
p = re.compile(regExp)
for line in out.splitlines():
m = p.match(line)
if m != None:
if m is not None:
size = int(m.group('size'))
position = int(m.group('position'))
logger.debug("Found codec private data at position: %s, size: %d" % (position, size))
logger.debug("Found codec private data at position: %s, size: %d", position, size)
found = True
mkvinfo.wait()
break
@@ -569,7 +572,7 @@ def parseScalingList(buf, bitPosition, size):
# The ISO/IEC H.264-201602 seems to take into account the case where the end of the deltas list is full of zeroes.
def writeScalingList(buf, bitPosition, size, matrix, optimized=False):
logger = logging.getLogger(__name__)
logger.debug('Dumping matrix: %s of size: %d, size parameter: %d.' % (matrix, len(matrix), size))
logger.debug('Dumping matrix: %s of size: %d, size parameter: %d.', matrix, len(matrix), size)
prev = 8
deltas = []
@@ -846,9 +849,13 @@ class SPS:
self.scaling_list={}
self.offset_for_ref_frame={}
# Compute options to pass to ffmpeg so as to reproduce the same SPS.
# TODO: ...
# Compute options to pass to ffmpeg so as to reproduce the same SPS.
# Very complex since some codec configuration are not provided by ffmpeg and/or libx264.
# This is only an attempt.
def ffmpegOptions(self, videoID=0):
logger = logging.getLogger(__name__)
x264opts = []
if self.profile_idc in [ 0x42, 0x4D, 0x64, 0x6E, 0x7A, 0xF4, 0x2C]:
@@ -865,14 +872,14 @@ class SPS:
elif self.profile_idc == 0xF4:
profile = 'high444'
else:
logger.error('Unknow profile: %x' % self.profile)
logger.error('Unknow profile: %x', self.profile_idc)
return []
level = '%d.%d' % (floor(self.level/10), self.level % 10)
level = '%d.%d' % (floor(self.level_idc/10), self.level_idc % 10)
x264opts.extend(['sps-id=%d' % self.seq_parameter_set_id] )
if self.bit_depth_chroma_minus8 not in [0,1,2,4,6,8]:
logger.error('Bit depth of chrominance is not supported: %d' % (self.bit_depth_chroma_minus8+8))
logger.error('Bit depth of chrominance is not supported: %d', self.bit_depth_chroma_minus8+8)
return []
if self.chroma_format_idc in range(0,4):
@@ -889,7 +896,7 @@ class SPS:
# YUV:4:4:4
pass
else:
logger.error('Unknow chrominance format: %x' % self.profile)
logger.error('Unknow chrominance format: %x', self.chroma_format_idc)
return []
res = ['-profile:v:%d' % videoID, self.profile_idc, '-level:v:%d' % videoID, level]
@@ -978,7 +985,7 @@ class SPS:
if self.vui_parameters_present_flag:
self.vui = VUI()
bitPosition = self.vui.fromBytes(buf,bitPosition)
logger.debug('VUI present: %s' % self.vui)
logger.debug('VUI present: %s', self.vui)
logger.debug('Parse end of SPS. Bit position: %d. Remaining bytes: %s.' % (bitPosition, hexdump.dump(buf[floor(bitPosition/8):], sep=':')))
@@ -1035,7 +1042,7 @@ class SPS:
bitPosition = writeUnsignedExpGolomb(buf, bitPosition, self.num_ref_frames_in_pic_order_cnt_cycle)
for i in range(0, self.num_ref_frames_in_pic_order_cnt_cycle):
v = self.offset_for_ref_frame[i]
bitPosition, v = writeUnsignedExpGolomb(buf, bitPosition)
bitPosition = writeUnsignedExpGolomb(buf, bitPosition, v)
bitPosition = writeUnsignedExpGolomb(buf, bitPosition, self.max_num_ref_frames)
bitPosition = writeBoolean(buf, bitPosition, self.gaps_in_frame_num_value_allowed_flag)
bitPosition = writeUnsignedExpGolomb(buf, bitPosition, self.pic_width_in_mbs_minus1)
@@ -1052,9 +1059,9 @@ class SPS:
bitPosition = writeUnsignedExpGolomb(buf, bitPosition, self.frame_crop_bottom_offset)
bitPosition = writeBoolean(buf, bitPosition, self.vui_parameters_present_flag)
if self.vui_parameters_present_flag:
logger.debug('SPS has VUI. Writing VUI at position: %d' % bitPosition)
logger.debug('SPS has VUI. Writing VUI at position: %d', bitPosition)
bitPosition = self.vui.toBytes(buf, bitPosition)
logger.debug('VUI written. New bit position: %d' % bitPosition)
logger.debug('VUI written. New bit position: %d', bitPosition)
bitPosition = writeRBSPTrailingBits(buf, bitPosition)
@@ -1129,7 +1136,7 @@ class PPS:
elif self.slice_group_map_type == 2:
for i in range(0, self.num_slice_groups_minus1):
bitPosition, v = readUnsignedExpGolomb(buf, bitPosition)
self.top_left.append[i] = v
self.top_left[i] = v
bitPosition, v = readUnsignedExpGolomb(buf, bitPosition)
self.bottom_right[i] = v
elif self.slice_group_map_type in [3,4,5]:
@@ -1204,7 +1211,7 @@ class PPS:
bitPosition = writeUnsignedExpGolomb(buf, bitPosition, v)
elif self.slice_group_map_type == 2:
for i in range(0, self.num_slice_groups_minus1):
v = self.top_left.append[i]
v = self.top_left[i]
bitPosition = writeUnsignedExpGolomb(buf, bitPosition, v)
v = self.bottom_right[i]
bitPosition = writeUnsignedExpGolomb(buf, bitPosition, v)
@@ -1545,7 +1552,7 @@ def parseMKVTree(mkvinfo, inputFile):
prevDepth = -1
for line in out.splitlines():
m = p.match(line)
if m == None:
if m is None:
logger.error("Impossible to match line: %s" % line)
else:
position = int(m.group('position'))
@@ -1668,7 +1675,7 @@ def changeEBMLElementSize(inputFile, position, addendum):
mask = mask>>1
if not found:
logger.error('Size of element type cannot be determined: %b' % elementType)
logger.error('Size of element type cannot be determined: %d', elementType)
exit(-1)
# We seek to size
@@ -1689,10 +1696,10 @@ def changeEBMLElementSize(inputFile, position, addendum):
mask = mask>>1
if not found:
logger.error('Size of data size cannot be determined: %b' % sizeHead)
logger.error('Size of data size cannot be determined: %d', sizeHead)
exit(-1)
else:
logger.info('Size of data size: %d.' % sizeOfDataSize)
logger.info('Size of data size: %d.', sizeOfDataSize)
lseek(infd, position, SEEK_SET)
oldSizeBuf = read(infd, sizeOfDataSize)
@@ -1842,7 +1849,7 @@ def getMovieDuration(ffprobe, inputFile):
return None
# ffprobe -loglevel quiet -select_streams v:0 -show_entries stream=width,height -of json ./talons.ts
# ffprobe -loglevel quiet -select_streams v:0 -show_entries stream=width,height -of json ./example.ts
def getVideoDimensions(ffprobe, inputFile):
logger = logging.getLogger(__name__)
@@ -1902,7 +1909,7 @@ def parseTimestamp(ts):
tsRegExp = r'^(?P<hour>[0-9]{1,2}):(?P<minute>[0-9]{1,2}):(?P<second>[0-9]{1,2})(\.(?P<us>[0-9]{1,6}))?$'
p = re.compile(tsRegExp)
m = p.match(ts)
if m == None:
if m is None:
logger.warning("Impossible to parse timestamp: %s" % ts)
return None
@@ -1911,13 +1918,13 @@ def parseTimestamp(ts):
minute = 0
second = 0
us = 0
if values['hour'] != None:
if values['hour'] is not None:
hour = int(values['hour'])
if values['minute'] != None:
if values['minute'] is not None:
minute = int(values['minute'])
if values['second'] != None:
if values['second'] is not None:
second = int(values['second'])
if values['us'] != None:
if values['us'] is not None:
us = int(values['us'])
if hour < 0 or hour > 23:
@@ -1943,7 +1950,7 @@ def parseTimeInterval(interval):
intervalRegExp = r'^(?P<hour1>[0-9]{1,2}):(?P<minute1>[0-9]{1,2}):(?P<second1>[0-9]{1,2})(\.(?P<ms1>[0-9]{1,3}))?-(?P<hour2>[0-9]{1,2}):(?P<minute2>[0-9]{1,2}):(?P<second2>[0-9]{1,2})(\.(?P<ms2>[0-9]{1,3}))?$'
p = re.compile(intervalRegExp)
m = p.match(interval)
if m == None:
if m is None:
logger.error("Impossible to parse time interval")
return None
@@ -1956,21 +1963,21 @@ def parseTimeInterval(interval):
minute2 = 0
second2 = 0
ms2 = 0
if values['hour1'] != None:
if values['hour1'] is not None:
hour1 = int(values['hour1'])
if values['minute1'] != None:
if values['minute1'] is not None:
minute1 = int(values['minute1'])
if values['second1'] != None:
if values['second1'] is not None:
second1 = int(values['second1'])
if values['ms1'] != None:
if values['ms1'] is not None:
ms1 = int(values['ms1'])
if values['hour2'] != None:
if values['hour2'] is not None:
hour2 = int(values['hour2'])
if values['minute2'] != None:
if values['minute2'] is not None:
minute2 = int(values['minute2'])
if values['second2'] != None:
if values['second2'] is not None:
second2 = int(values['second2'])
if values['ms2'] != None:
if values['ms2'] is not None:
ms2 = int(values['ms2'])
if hour1 < 0 or hour1 > 23:
@@ -2046,39 +2053,43 @@ def ffmpegConvert(ffmpeg, ffprobe, inputFile, inputFormat, outputFile, outputFor
params.extend(['-r:0', '25', '-f', outputFormat, '/proc/self/fd/%d' % outfd])
logger.debug('Executing %s' % params)
with Popen(params, stdout=PIPE, close_fds=False) as ffmpeg:
pb = tqdm(TextIOWrapper(ffmpeg.stdout, encoding="utf-8"), total=int(duration/timedelta(seconds=1)), unit='s', desc='Conversion')
for line in pb:
if line.startswith('out_time='):
ts = line.split('=')[1].strip()
ts = parseTimestamp(ts)
if ts != None:
if ts is not None:
pb.n = int(ts/timedelta(seconds=1))
pb.update()
status = ffmpeg.wait()
if status != 0:
logger.error('Conversion failed with status code: %d' % status)
logger.error('Conversion failed with status code: %d', status)
def getTSFrame(frame):
logger = logging.getLogger(__name__)
if 'pts_time' in frame:
pts_time = float(frame['pts_time'])
elif 'pkt_pts_time' in frame:
pts_time = float(frame['pkt_pts_time'])
else:
logger.error('Impossible to find timestamp of frame %s' % frame)
logger.error('Impossible to find timestamp of frame %s', frame)
return None
ts = timedelta(seconds=pts_time)
return ts
def getPacketDuration(packet):
logger = logging.getLogger(__name__)
if 'duration' in packet:
duration = int(packet['duration'])
elif 'pkt_duration' in packet:
duration = int(packet['pkt_duration'])
else:
logger.error('Impossible to find duration of packet %s' % packet)
logger.error('Impossible to find duration of packet %s', packet)
return None
return duration
@@ -2107,7 +2118,7 @@ def getFramesInStream(ffprobe, inputFile, begin, end, streamKind, subStreamId=0)
frames = frames['frames']
for frame in frames:
ts = getTSFrame(frame)
if ts == None:
if ts is None:
return None
if begin <= ts and ts <= end:
tmp[ts]=frame
@@ -2134,12 +2145,12 @@ def getNearestIDRFrame(ffprobe, inputFile, timestamp, before=True, delta=timedel
infd = inputFile.fileno()
set_inheritable(infd, True)
logger.debug('Looking for IDR frame in [%s, %s]' % (tbegin, tend))
logger.debug('Looking for IDR frame in [%s, %s]', tbegin, tend)
idrs = []
# Retains only IDR frame
with Popen([ffprobe, '-loglevel', 'quiet', '-read_intervals', ('%s%%%s' %(begin, end)), '-skip_frame', 'nokey', '-show_entries', 'frame', '-select_streams', 'v:0', '-of', 'json', '/proc/self/fd/%d' % infd], stdout=PIPE, close_fds=False) as ffprobe:
with Popen([ffprobe, '-loglevel', 'quiet', '-read_intervals', ('%s%%%s' %(tbegin, tend)), '-skip_frame', 'nokey', '-show_entries', 'frame', '-select_streams', 'v:0', '-of', 'json', '/proc/self/fd/%d' % infd], stdout=PIPE, close_fds=False) as ffprobe:
out, _ = ffprobe.communicate()
frames = json.load(BytesIO(out))
status = ffprobe.wait()
@@ -2151,12 +2162,12 @@ def getNearestIDRFrame(ffprobe, inputFile, timestamp, before=True, delta=timedel
frames = frames['frames']
for frame in frames:
ts = getTSFrame(frame)
if ts == None:
if ts is None:
return None
if begin <= ts and ts <= end:
if tbegin <= ts and ts <= tend:
idrs.append(frame)
else:
logger.error('Impossible to retrieve IDR frames inside file around [%s,%s]' % (begin, end))
logger.error('Impossible to retrieve IDR frames inside file around [%s,%s]', tbegin, tend)
return None
@@ -2186,7 +2197,7 @@ def getNearestIFrame(ffprobe, inputFile, timestamp, before=True, deltaMax=timede
logger.debug('Looking for an iframe in [%s, %s]' % (tbegin, tend))
frames = getFramesInStream(ffprobe, inputFile=inputFile, begin=tbegin, end=tend, streamKind='v')
if frames == None:
if frames is None:
logger.debug('Found no frame in [%s, %s]' % (tbegin, tend))
delta+=timedelta(seconds=1)
continue
@@ -2199,7 +2210,7 @@ def getNearestIFrame(ffprobe, inputFile, timestamp, before=True, deltaMax=timede
found = False
for frame in iframes:
ts = getTSFrame(frame)
if ts == None:
if ts is None:
logger.warning('I-frame with no timestamp: %s' % frame)
continue
@@ -2218,12 +2229,12 @@ def getNearestIFrame(ffprobe, inputFile, timestamp, before=True, deltaMax=timede
delta+=timedelta(seconds=1)
continue
if iframe != None:
if iframe is not None:
its = getTSFrame(iframe)
nbFrames = 0
for frame in frames:
ts = getTSFrame(frame)
if ts == None:
if ts is None:
logger.warning('Frame without timestamp: %s' % frame)
continue
@@ -2263,7 +2274,7 @@ def extractMKVPart(mkvmerge, inputFile, outputFile, begin, end):
if line.startswith('Progress :'):
p = re.compile('^Progress : (?P<progress>[0-9]{1,3})%$')
m = p.match(line)
if m == None:
if m is None:
logger.error('Impossible to parse progress')
pb.update(int(m['progress'])-pb.n)
elif line.startswith('Warning'):
@@ -2367,7 +2378,7 @@ def dumpPPM(pictures, prefix, temporaries):
if magic == 'P6\n':
pattern = re.compile('^(?P<width>[0-9]+) (?P<height>[0-9]+)\n$')
m = pattern.match(dimensions)
if m != None:
if m is not None:
width = int(m['width'])
height = int(m['height'])
else:
@@ -2424,7 +2435,8 @@ def extractAllStreams(ffmpeg, ffprobe, inputFile, begin, end, streams, filesPref
level = int(stream['level'])
level = '%d.%d' % (floor(level/10), level%10)
chromaLocation = stream['chroma_location']
fieldOrder = stream['field_order']
fieldOrder = stream
interlacedOptions = []
if fieldOrder == 'progressive':
interlacedOptions = ['-field_order', '0']
elif fieldOrder == 'tt':
@@ -2441,7 +2453,7 @@ def extractAllStreams(ffmpeg, ffprobe, inputFile, begin, end, streams, filesPref
# https://superuser.com/questions/907933/correct-aspect-ratio-without-re-encoding-video-file
codec = stream['codec_name']
imagesBytes, memfd = extractPictures(ffmpeg, inputFile=inputFile, begin=begin, nbFrames=nbFrames, width=width, height=height)
if imagesBytes == None:
if imagesBytes is None:
exit(-1)
memfds.append(memfd)
@@ -2475,7 +2487,7 @@ def extractAllStreams(ffmpeg, ffprobe, inputFile, begin, end, streams, filesPref
logger.debug("Found %d packets to be extracted from audio track." % nbPackets)
if(nbPackets > 0):
packetDuration = getPacketDuration(packets[0])
if packetDuration == None:
if packetDuration is None:
return None
else:
packetDuration = 0
@@ -2486,7 +2498,7 @@ def extractAllStreams(ffmpeg, ffprobe, inputFile, begin, end, streams, filesPref
soundBytes, memfd = extractSound(ffmpeg=ffmpeg, inputFile=inputFile, begin=begin, nbPackets=nbPackets, packetDuration=packetDuration, outputFileName=tmpname, sampleRate=sampleRate, nbChannels=nbChannels)
if soundBytes == None:
if soundBytes is None:
exit(-1)
memfds.append(memfd)
@@ -2633,7 +2645,7 @@ def mergeMKVs(mkvmerge, inputs, outputName, concatenate=True, timestamps=None):
fds.append(fd)
set_inheritable(fd, True)
# If we pass a timestamps file associated with the considered track, use it.
if timestamps != None and partNum in timestamps:
if timestamps is not None and partNum in timestamps:
tsfd = timestamps[partNum].fileno()
lseek(tsfd, 0, SEEK_SET)
fds.append(tsfd)
@@ -2661,7 +2673,7 @@ def mergeMKVs(mkvmerge, inputs, outputName, concatenate=True, timestamps=None):
if line.startswith('Progress :'):
p = re.compile('^Progress : (?P<progress>[0-9]{1,3})%$')
m = p.match(line)
if m == None:
if m is None:
logger.error('Impossible to parse progress')
pb.n = int(m['progress'])
pb.update()
@@ -2728,7 +2740,7 @@ def extractTrackFromMKV(mkvextract, inputFile, index, outputFile, timestamps):
if line.startswith('Progress :'):
p = re.compile('^Progress : (?P<progress>[0-9]{1,3})%$')
m = p.match(line)
if m == None:
if m is None:
logger.error('Impossible to parse progress')
pb.update(int(m['progress'])-pb.n)
pb.update(100-pb.n)
@@ -2763,7 +2775,7 @@ def removeVideoTracksFromMKV(mkvmerge, inputFile, outputFile):
if line.startswith('Progress :'):
p = re.compile('^Progress : (?P<progress>[0-9]{1,3})%$')
m = p.match(line)
if m == None:
if m is None:
logger.error('Impossible to parse progress')
pb.update(int(m['progress'])-pb.n)
pb.update(100-pb.n)
@@ -2810,7 +2822,7 @@ def remuxSRTSubtitles(mkvmerge, inputFile, outputFileName, subtitles):
if line.startswith('Progress :'):
p = re.compile('^Progress : (?P<progress>[0-9]{1,3})%$')
m = p.match(line)
if m == None:
if m is None:
logger.error('Impossible to parse progress')
pb.n = int(m['progress'])
pb.update()
@@ -2844,7 +2856,7 @@ def concatenateH264Parts(h264parts, output):
lseek(fd, 0, SEEK_SET)
while True:
buf = read(fd, 1000000)
if buf == None or len(buf) == 0:
if buf is None or len(buf) == 0:
break
pos = 0
while pos < len(buf):
@@ -2882,6 +2894,7 @@ def concatenateH264TSParts(h264TSParts, output):
first = False
def doCoarseProcessing(ffmpeg, ffprobe, mkvmerge, inputFile, begin, end, nbFrames, frameRate, filesPrefix, streams, width, height, temporaries, dumpMemFD):
logger = logging.getLogger(__name__)
# Internal video with all streams (video, audio and subtitles)
internalMKVName = '%s.mkv' % filesPrefix
@@ -2889,7 +2902,7 @@ def doCoarseProcessing(ffmpeg, ffprobe, mkvmerge, inputFile, begin, end, nbFrame
try:
internalMKV = open(internalMKVName, 'w+')
except IOError:
logger.error('Impossible to create file: %s' % internalMKVName)
logger.error('Impossible to create file: %s', internalMKVName)
exit(-1)
# Extract internal part of MKV
@@ -2923,18 +2936,18 @@ def main():
logger.debug('Arguments: %s' % args)
if args.coarse and args.threshold != None:
if args.coarse and args.threshold is not None:
logger.error('--coarse and threshold arguments are exclusive.')
exit(-1)
if (not args.coarse) and args.threshold == None:
if (not args.coarse) and args.threshold is None:
args.threshold = 0
allOptionalTools, paths = checkRequiredTools()
# Flatten args.parts
intervals = []
if args.parts != None:
if args.parts is not None:
for part in args.parts:
for subpart in part:
intervals.append(subpart)
@@ -2943,7 +2956,7 @@ def main():
# Parse each interval
for interval in intervals:
ts1, ts2 = parseTimeInterval(interval)
if ts1 == None or ts2 == None:
if ts1 is None or ts2 is None:
logger.error("Illegal time interval: %s" % interval)
exit(-1)
parts.append((ts1,ts2))
@@ -2975,15 +2988,15 @@ def main():
formatOfFile = getFormat(paths['ffprobe'], inputFile)
if formatOfFile == None:
if formatOfFile is None:
exit(-1)
duration = timedelta(seconds=float(formatOfFile['duration']))
logger.info("Durée de l'enregistrement: %s" % duration)
if args.framerate == None:
if args.framerate is None:
frameRate = getFrameRate(paths['ffprobe'], inputFile)
if frameRate == None:
if frameRate is None:
logger.error('Impossible to estimate frame rate !')
exit(-1)
else:
@@ -3053,7 +3066,7 @@ def main():
else:
mainVideo = None
if mainVideo == None:
if mainVideo is None:
logger.error('Impossible to find main video stream.')
exit(-1)
@@ -3106,26 +3119,26 @@ def main():
# Get the nearest I-frame whose timestamp is greater or equal to the beginning.
headFrames = getNearestIFrame(paths['ffprobe'], mkv, ts1, before=False)
if headFrames == None:
if headFrames is None:
exit(-1)
# Get the nearest I-frame whose timestamp ...
# TODO: wrong here ...
tailFrames = getNearestIFrame(paths['ffprobe'], mkv, ts2, before=True)
if tailFrames == None:
if tailFrames is None:
exit(-1)
nbHeadFrames, headIFrame = headFrames
nbTailFrames, tailIFrame = tailFrames
logger.info("Found %d frames between beginning of current part and first I-frame" % nbHeadFrames)
logger.info("Found %d frames between last I-frame and end of current part" % nbTailFrames)
logger.info("Found %d frames between beginning of current part and first I-frame", nbHeadFrames)
logger.info("Found %d frames between last I-frame and end of current part", nbTailFrames)
headIFrameTS = getTSFrame(headIFrame)
if headIFrameTS == None:
if headIFrameTS is None:
exit(-1)
tailIFrameTS = getTSFrame(tailIFrame)
if tailIFrameTS == None:
if tailIFrameTS is None:
exit(-1)
checks.append(pos+headIFrameTS-ts1)
@@ -3145,13 +3158,13 @@ def main():
h264Head, h264HeadTS, mkvHead = extractAllStreams(ffmpeg=paths['ffmpeg'], ffprobe=paths['ffprobe'], inputFile=mkv, begin=ts1, end=headIFrameTS, nbFrames=nbHeadFrames-1, frameRate=frameRate, filesPrefix='part-%d-head' % (partnum), streams=streams, width=width, height=height, temporaries=temporaries, dumpMemFD=args.dump)
# If we are not at an exact boundary:
if mkvHead != None:
if mkvHead is not None:
subparts.append(mkvHead)
if h264Head != None:
if h264Head is not None:
avcconfig = getAvcConfigFromH264(h264Head)
otherAvcConfigs.append(avcconfig)
h264parts.append(h264Head)
if h264HeadTS != None:
if h264HeadTS is not None:
h264TS.append(h264HeadTS)
@@ -3168,25 +3181,25 @@ def main():
try:
internalMKV = open(internalMKVName, 'w+')
except IOError:
logger.error('Impossible to create file: %s' % internalMKVName)
logger.error('Impossible to create file: %s', internalMKVName)
exit(-1)
try:
internalNoVideoMKV = open(internalNoVideoMKVName, 'w+')
except IOError:
logger.error('Impossible to create file: %s' % internalNoVideoMKVName)
logger.error('Impossible to create file: %s', internalNoVideoMKVName)
exit(-1)
try:
internalH264 = open(internalH264Name, 'w+')
except IOError:
logger.error('Impossible to create file: %s' % internalH264Name)
logger.error('Impossible to create file: %s', internalH264Name)
exit(-1)
try:
internalH264TS = open(internalH264TSName, 'w+')
except IOError:
logger.error('Impossible to create file: %s' % internalH264TSName)
logger.error('Impossible to create file: %s', internalH264TSName)
exit(-1)
# logger.info('Merge header, middle and trailer subpart into: %s' % internalMKVName)
@@ -3199,7 +3212,7 @@ def main():
extractTrackFromMKV(mkvextract=paths['mkvextract'], inputFile=internalMKV, index=0, outputFile=internalH264, timestamps=internalH264TS)
# Remove video track from internal part of MKV
logger.info('Remove video track from %s' % internalMKVName)
logger.info('Remove video track from %s', internalMKVName)
removeVideoTracksFromMKV(mkvmerge=paths['mkvmerge'], inputFile=internalMKV, outputFile=internalNoVideoMKV)
temporaries.append(internalMKV)
@@ -3215,16 +3228,16 @@ def main():
# We extract all frames between the I-frame (including it) upto the end.
h264Tail, h264TailTS, mkvTail = extractAllStreams(ffmpeg=paths['ffmpeg'], ffprobe=paths['ffprobe'], inputFile=mkv, begin=tailIFrameTS, end=ts2, nbFrames=nbTailFrames, frameRate=frameRate, filesPrefix='part-%d-tail' % (partnum), streams=streams, width=width, height=height, temporaries=temporaries, dumpMemFD=args.dump)
if mkvTail != None:
if mkvTail is not None:
subparts.append(mkvTail)
if h264Tail != None:
if h264Tail is not None:
avcconfig = getAvcConfigFromH264(h264Tail)
otherAvcConfigs.append(avcconfig)
h264parts.append(h264Tail)
if h264TailTS != None:
if h264TailTS is not None:
h264TS.append(h264TailTS)
logger.info('Merging MKV: %s' % subparts)
logger.info('Merging MKV: %s', subparts)
part = mergeMKVs(mkvmerge=paths['mkvmerge'], inputs=subparts, outputName="part-%d.mkv" % partnum, concatenate=True)
mkvparts.append(part)
temporaries.append(part)
@@ -3237,7 +3250,7 @@ def main():
# When using coarse option there is a single AVC configuration.
for avcConfig in otherAvcConfigs:
mainAvcConfig.merge(avcConfig)
logger.debug('Merged AVC configuration: %s' % mainAvcConfig)
logger.debug('Merged AVC configuration: %s', mainAvcConfig)
nbMKVParts = len(mkvparts)
if nbMKVParts > 0:
@@ -3328,12 +3341,12 @@ def main():
try:
idx = open(idxName,'r')
except IOError:
logger.error("Impossible to open %s." % idxName)
logger.error("Impossible to open %s.", idxName)
exit(-1)
try:
sub = open(subName,'r')
except IOError:
logger.error("Impossible to open %s." % subName)
logger.error("Impossible to open %s.", subName)
exit(-1)
temporaries.append(idx)