On est capable d'encoder les tête et fin de partie avec presque les mêmes réglages que l'original.
This commit is contained in:
214
removeads.py
214
removeads.py
@@ -25,8 +25,38 @@ class SupportedFormat(IntEnum):
|
|||||||
elif self is SupportedFormat.Matroska:
|
elif self is SupportedFormat.Matroska:
|
||||||
return 'matroska,webm'
|
return 'matroska,webm'
|
||||||
else:
|
else:
|
||||||
return "Unsupported format"
|
return 'Unsupported format'
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class ColorSpace(IntEnum):
|
||||||
|
BT709=0,
|
||||||
|
FCC=1,
|
||||||
|
BT601=2,
|
||||||
|
BT470=3,
|
||||||
|
BT470BG=4,
|
||||||
|
SMPTE170M=5,
|
||||||
|
SMPTE240M=6,
|
||||||
|
BT2020=7
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self is ColorSpace.BT709:
|
||||||
|
return 'bt709'
|
||||||
|
elif self is ColorSpace.FCC:
|
||||||
|
return 'fcc'
|
||||||
|
elif self is ColorSpace.BT601:
|
||||||
|
return 'bt601'
|
||||||
|
elif self is ColorSpace.BT470:
|
||||||
|
return 'bt470'
|
||||||
|
elif self is ColorSpace.BT470BG:
|
||||||
|
return 'bt470bg'
|
||||||
|
elif self is ColorSpace.SMPTE170M:
|
||||||
|
return 'smpte170m'
|
||||||
|
elif self is ColorSpace.SMPTE240M:
|
||||||
|
return 'smpte240m'
|
||||||
|
elif self is ColorSpace.BT2020:
|
||||||
|
return 'bt2020'
|
||||||
|
else:
|
||||||
|
return 'Unsupported color space'
|
||||||
|
|
||||||
def getFormat(inputFile):
|
def getFormat(inputFile):
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -75,6 +105,26 @@ def ffmpegConvert(inputFile, inputFormat, outputFile, outputFormat):
|
|||||||
if line.startswith('out_time='):
|
if line.startswith('out_time='):
|
||||||
print(line, end='')
|
print(line, end='')
|
||||||
|
|
||||||
|
def getFramesInStream(inputFile, begin, end, streamKind, subStreamId=0):
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
infd = inputFile.fileno()
|
||||||
|
set_inheritable(infd, True)
|
||||||
|
|
||||||
|
with Popen(['ffprobe', '-loglevel', 'quiet', '-read_intervals', ('%s%%%s' %(begin, end)), '-show_entries', 'frame', '-select_streams', '%s:%d' % (streamKind, subStreamId), '-of', 'json', '/proc/self/fd/%d' % infd], stdout=PIPE, close_fds=False) as ffprobe:
|
||||||
|
out, _ = ffprobe.communicate()
|
||||||
|
frames = json.load(BytesIO(out))
|
||||||
|
res = []
|
||||||
|
if 'frames' in frames:
|
||||||
|
frames = frames['frames']
|
||||||
|
for frame in frames:
|
||||||
|
ts = timedelta(seconds=float(frame['pts_time']))
|
||||||
|
if begin <= ts and ts <= end:
|
||||||
|
res.append(frame)
|
||||||
|
return res
|
||||||
|
else:
|
||||||
|
logger.error('Impossible to retrieve frames inside file around [%s,%s]' % (begin, end))
|
||||||
|
return None
|
||||||
|
|
||||||
def getNearestIFrame(inputFile, timestamp, before=True, delta=timedelta(seconds=2)):
|
def getNearestIFrame(inputFile, timestamp, before=True, delta=timedelta(seconds=2)):
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -89,11 +139,10 @@ def getNearestIFrame(inputFile, timestamp, before=True, delta=timedelta(seconds=
|
|||||||
|
|
||||||
logger.debug('Looking for iframe in [%s, %s]' % (tbegin, tend))
|
logger.debug('Looking for iframe in [%s, %s]' % (tbegin, tend))
|
||||||
|
|
||||||
with Popen(['ffprobe', '-loglevel', 'quiet', '-read_intervals', ('%s%%%s' %(tbegin, tend)), '-show_entries', 'frame', '-select_streams', 'v', '-of', 'json', '/proc/self/fd/%d' % infd], stdout=PIPE, close_fds=False) as ffprobe:
|
frames = getFramesInStream(inputFile=inputFile, begin=tbegin, end=tend, streamKind='v')
|
||||||
out, _ = ffprobe.communicate()
|
if frames == None:
|
||||||
frames = json.load(BytesIO(out))
|
return None
|
||||||
if 'frames' in frames:
|
|
||||||
frames = frames['frames']
|
|
||||||
iframes = []
|
iframes = []
|
||||||
for frame in frames:
|
for frame in frames:
|
||||||
if frame['pict_type'] == 'I':
|
if frame['pict_type'] == 'I':
|
||||||
@@ -127,10 +176,6 @@ def getNearestIFrame(inputFile, timestamp, before=True, delta=timedelta(seconds=
|
|||||||
logger.error("Impossible to find I-frame around: %s" % timestamp)
|
logger.error("Impossible to find I-frame around: %s" % timestamp)
|
||||||
|
|
||||||
return(nbFrames-1, iframe)
|
return(nbFrames-1, iframe)
|
||||||
else:
|
|
||||||
logger.error('Impossible to retrieve video frames inside file around [%s,%s]' % (tbegin, tend))
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def extractMKVPart(inputFile, outputFile, begin, end):
|
def extractMKVPart(inputFile, outputFile, begin, end):
|
||||||
inputFile.seek(0,0)
|
inputFile.seek(0,0)
|
||||||
@@ -147,19 +192,99 @@ def extractPictures(inputFile, begin, nbFrames, prefix, width=640, height=480):
|
|||||||
inputFile.seek(0,0)
|
inputFile.seek(0,0)
|
||||||
infd = inputFile.fileno()
|
infd = inputFile.fileno()
|
||||||
set_inheritable(infd, True)
|
set_inheritable(infd, True)
|
||||||
with Popen(['ffmpeg', '-loglevel', 'quiet', '-ss', '%s'%begin, '-i', '/proc/self/fd/%d' % infd, '-s', '%dx%d'%(width, height), '-vframes', '%d'%nbFrames, '-c:v', 'ppm', '-f', 'image2', '%s-%%03d.ppm' % prefix], stdout=PIPE, close_fds=False) as ffmpeg:
|
with Popen(['ffmpeg', '-y', '-loglevel', 'quiet', '-ss', '%s'%begin, '-i', '/proc/self/fd/%d' % infd, '-s', '%dx%d'%(width, height), '-vframes', '%d'%nbFrames, '-c:v', 'ppm', '-f', 'image2', '%s-%%03d.ppm' % prefix], stdout=PIPE, close_fds=False) as ffmpeg:
|
||||||
for line in TextIOWrapper(ffmpeg.stdout, encoding="utf-8"):
|
for line in TextIOWrapper(ffmpeg.stdout, encoding="utf-8"):
|
||||||
print(line, end='')
|
print(line, end='')
|
||||||
|
|
||||||
def extractSound(inputFile, begin, outputFile, channel=0, nbPackets=10, sampleRate=48000, nbChannels=2):
|
def extractSound(inputFile, begin, outputFile, subChannel=0, nbPackets=0, sampleRate=48000, nbChannels=2):
|
||||||
inputFile.seek(0,0)
|
inputFile.seek(0,0)
|
||||||
|
outputFile.seek(0,0)
|
||||||
infd = inputFile.fileno()
|
infd = inputFile.fileno()
|
||||||
|
outfd = outputFile.fileno()
|
||||||
set_inheritable(infd, True)
|
set_inheritable(infd, True)
|
||||||
with Popen(['ffmpeg', '-loglevel', 'quiet', '-ss', '%s'%begin, '-i', '/proc/self/fd/%d' % infd, '-frames:a:%d' % channel, '%d' % nbPackets,
|
set_inheritable(outfd, True)
|
||||||
'-c:a', 'pcm_s32le', '-sample_rate', '%d' % 48000, '-channels', '%d' % 2, '-f', 's32le', '%s.pcm' % outputFile], stdout=PIPE, close_fds=False) as ffmpeg:
|
with Popen(['ffmpeg', '-y', '-loglevel', 'quiet', '-ss', '%s'%begin, '-i', '/proc/self/fd/%d' % infd, '-frames:a:%d' % subChannel, '%d' % nbPackets,
|
||||||
|
'-c:a', 'pcm_s32le', '-sample_rate', '%d' % sampleRate, '-channels', '%d' % nbChannels, '-f', 's32le', '/proc/self/fd/%d' % outfd], stdout=PIPE, close_fds=False) as ffmpeg:
|
||||||
for line in TextIOWrapper(ffmpeg.stdout, encoding="utf-8"):
|
for line in TextIOWrapper(ffmpeg.stdout, encoding="utf-8"):
|
||||||
print(line, end='')
|
print(line, end='')
|
||||||
|
|
||||||
|
def extractAllStreams(inputFile, begin, end, streams, filesPrefix, nbFrames, width, height):
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
# encoderParams = [ 'ffmpeg', '-y', '-loglevel', 'quiet' ]
|
||||||
|
encoderParams = [ 'ffmpeg', '-y' ]
|
||||||
|
inputParams = []
|
||||||
|
codecsParams = []
|
||||||
|
|
||||||
|
if begin < end:
|
||||||
|
videoID=0
|
||||||
|
audioID=0
|
||||||
|
subTitleID=0
|
||||||
|
audioFiles = {}
|
||||||
|
for stream in streams:
|
||||||
|
if stream['codec_type'] == 'video':
|
||||||
|
print("Extracting video stream: %s" % stream)
|
||||||
|
frameRate = stream['r_frame_rate']
|
||||||
|
sar = stream['sample_aspect_ratio']
|
||||||
|
dar = stream['display_aspect_ratio']
|
||||||
|
pixelFormat = stream['pix_fmt']
|
||||||
|
colorRange = stream['color_range']
|
||||||
|
colorSpace =stream['color_space']
|
||||||
|
colorTransfer = stream['color_transfer']
|
||||||
|
colorPrimaries = stream['color_primaries']
|
||||||
|
codec = stream['codec_name']
|
||||||
|
extractPictures(inputFile=inputFile, begin=begin, nbFrames=nbFrames, prefix="%s-%d" % (filesPrefix, videoID), width=width, height=height)
|
||||||
|
inputParams.extend(['-i', '%s-%d-%%03d.ppm' % (filesPrefix, videoID)])
|
||||||
|
codecsParams.extend(['-c:v:%d' % videoID, codec, '-pix_fmt', pixelFormat, '-colorspace:v:%d' % videoID, colorSpace, '-color_primaries:v:%d' % videoID, colorPrimaries,
|
||||||
|
'-color_trc:v:%d' % videoID, colorTransfer, '-color_range:v:%d' % videoID, colorRange ])
|
||||||
|
videoID=videoID+1
|
||||||
|
elif stream['codec_type'] == 'audio':
|
||||||
|
print("Extracting audio stream: %s" % stream)
|
||||||
|
sampleRate = int(stream['sample_rate'])
|
||||||
|
nbChannels = int(stream['channels'])
|
||||||
|
bitRate = int(stream['bit_rate'])
|
||||||
|
codec = stream['codec_name']
|
||||||
|
if 'tags' in stream:
|
||||||
|
if 'language' in stream['tags']:
|
||||||
|
codecsParams.extend(['-metadata:s:a:%d' % audioID, 'language=%s' % stream['tags']['language']])
|
||||||
|
packets = getFramesInStream(inputFile=inputFile, begin=begin, end=end, streamKind='a', subStreamId=audioID)
|
||||||
|
nbPackets = len(packets)
|
||||||
|
print("Found %d packets to be extracted from audio track." % nbPackets)
|
||||||
|
audioFiles[audioID] = open('%s-%d.pcm' % (filesPrefix,audioID), 'w')
|
||||||
|
# TODO: test if successfully openened
|
||||||
|
extractSound(inputFile=inputFile, begin=begin, nbPackets=nbPackets, outputFile=audioFiles[audioID], sampleRate=sampleRate, nbChannels=nbChannels)
|
||||||
|
inputParams.extend(['-f', 's32le', '-ar', '%d'%sampleRate, '-ac', '%d'%nbChannels, '-i', '/proc/self/fd/%d' % audioFiles[audioID].fileno()])
|
||||||
|
codecsParams.extend(['-c:a:%d' % audioID, codec, '-b:a:%d' % audioID, '%d' % bitRate])
|
||||||
|
audioID=audioID+1
|
||||||
|
elif stream['codec_type'] == 'subtitle':
|
||||||
|
# TODO: what can be done ?
|
||||||
|
subTitleID=subTitleID+1
|
||||||
|
else:
|
||||||
|
logger.info("Unknown stream type: %s" % stream['codec_type'])
|
||||||
|
|
||||||
|
# Example:
|
||||||
|
# ffmpeg -framerate 25.85 -i image-%02d.ppm -f s32le -ar 48000 -ac 2 -i ./audio-1.pcm -c:a eac3 -b:a 128k -c:v libx264 -crf 25.85 -vf "scale=1920:1080,format=yuv420p" -colorspace:v "bt709" -color_primaries:v "bt709" -color_trc:v "bt709" -color_range:v "tv" -top 1 -flags:v +ilme+ildct -bsf:v h264_mp4toannexb,dump_extra=keyframe -metadata MAJOR_BRAND=isom -metadata MINOR_VERSION=512 -movflags +faststart cut-1.mkv
|
||||||
|
|
||||||
|
# Create a new MKV movie with all streams that have been extracted.
|
||||||
|
encoderParams.extend(inputParams)
|
||||||
|
for index in range(0,videoID+audioID+subTitleID-1):
|
||||||
|
encoderParams.extend(['-map', '%d' % index])
|
||||||
|
encoderParams.extend(codecsParams)
|
||||||
|
# output = open('out.mkv','w')
|
||||||
|
# outfd = output.fileno()
|
||||||
|
encoderParams.extend(['-f', 'matroska', 'out.mkv'])
|
||||||
|
|
||||||
|
print(encoderParams)
|
||||||
|
|
||||||
|
with Popen(encoderParams, stdout=PIPE, close_fds=False) as ffmpeg:
|
||||||
|
for line in TextIOWrapper(ffmpeg.stdout, encoding="utf-8"):
|
||||||
|
print(line, end='')
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Nothing to be done. We are already at a i-frame boundary.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def parseTimeInterval(interval):
|
def parseTimeInterval(interval):
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -338,58 +463,29 @@ def main():
|
|||||||
for ts1, ts2 in parts:
|
for ts1, ts2 in parts:
|
||||||
partnum = partnum + 1
|
partnum = partnum + 1
|
||||||
|
|
||||||
preFrame = getNearestIFrame(mkv, ts1, before=False)
|
headFrames = getNearestIFrame(mkv, ts1, before=False)
|
||||||
if preFrame == None:
|
if headFrames == None:
|
||||||
exit(-1)
|
exit(-1)
|
||||||
|
|
||||||
postFrame = getNearestIFrame(mkv, ts2, before=True)
|
tailFrames = getNearestIFrame(mkv, ts2, before=True)
|
||||||
if postFrame == None:
|
if tailFrames == None:
|
||||||
exit(-1)
|
exit(-1)
|
||||||
|
|
||||||
nbPreFrame, preIFrame = preFrame
|
nbHeadFrames, headIFrame = headFrames
|
||||||
nbPostFrame, postIFrame = postFrame
|
nbTailFrames, tailIFrame = tailFrames
|
||||||
|
|
||||||
print("Found pre I-frame and %d frames between: %s" % (nbPreFrame, preIFrame))
|
print("Found head I-frame and %d frames between: %s" % (nbHeadFrames, headIFrame))
|
||||||
print("Found I-frame and %d frames between: %s" % (nbPostFrame, postIFrame))
|
print("Found I-frame and %d frames between: %s" % (nbTailFrames, tailIFrame))
|
||||||
|
|
||||||
preIFrameTS = timedelta(seconds=float(preIFrame['pts_time']))
|
headIFrameTS = timedelta(seconds=float(headIFrame['pts_time']))
|
||||||
postIFrameTS = timedelta(seconds=float(postIFrame['pts_time']))
|
tailIFrameTS = timedelta(seconds=float(tailIFrame['pts_time']))
|
||||||
|
|
||||||
if ts1 < preIFrameTS:
|
extractAllStreams(inputFile=mkv, begin=ts1, end=headIFrameTS, nbFrames=nbHeadFrames, filesPrefix='part-%d-head' % (partnum), streams=streams, width=width, height=height)
|
||||||
for stream in streams:
|
extractAllStreams(inputFile=mkv, begin=tailIFrameTS, end=ts2, nbFrames=nbTailFrames, filesPrefix='part-%d-tail' % (partnum), streams=streams, width=width, height=height)
|
||||||
if stream['codec_type'] == 'video':
|
|
||||||
extractPictures(inputFile=mkv, begin=ts1, nbFrames=nbPreFrame, prefix="pre-part-%d" % partnum, width=width, height=height)
|
|
||||||
elif stream['codec_type'] == 'audio':
|
|
||||||
print("Extracting audio stream: %s" % stream)
|
|
||||||
sampleRate = stream['sample_rate']
|
|
||||||
nbChannel = stream['channels']
|
|
||||||
extractSound(inputFile=mkv, begin=ts1, nbPackets=nbPreFrame, outputFile="pre-part-%d" % partnum, sampleRate=sampleRate, nbChannels=nbChannels)
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Nothing to do
|
|
||||||
pass
|
|
||||||
|
|
||||||
if postIFrameTS < ts2:
|
|
||||||
for stream in streams:
|
|
||||||
if stream['codec_type'] == 'video':
|
|
||||||
extractPictures(inputFile=mkv, begin=postIFrameTS, nbFrames=nbPostFrame, prefix="post-part-%d" % partnum, width=width, height=height)
|
|
||||||
elif stream['codec_type'] == 'audio':
|
|
||||||
print("Extracting audio stream: %s" % stream)
|
|
||||||
sampleRate = stream['sample_rate']
|
|
||||||
nbChannel = stream['channels']
|
|
||||||
# TODO: how many packets should be dumped ...
|
|
||||||
# TODO: take into account multiple sound tracks ...
|
|
||||||
extractSound(inputFile=mkv, begin=postIFrameTS, nbPackets=nbPostFrame, outputFile="post-part-%d" % partnum, sampleRate=sampleRate, nbChannels=nbChannels)
|
|
||||||
else:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
# Nothing to do !
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Creating MKV file that corresponds to current part between I-frames
|
# Creating MKV file that corresponds to current part between I-frames
|
||||||
with open('part-%d.mkv' % partnum, 'w') as partmkv:
|
with open('part-%d-internal.mkv' % partnum, 'w') as partmkv:
|
||||||
extractMKVPart(inputFile=mkv, outputFile=partmkv, begin=preIFrameTS, end=postIFrameTS)
|
extractMKVPart(inputFile=mkv, outputFile=partmkv, begin=headIFrameTS, end=tailIFrameTS)
|
||||||
|
|
||||||
# Trouver l'estampille de la trame 'I' la plus proche (mais postérieure) au début de la portion.
|
# Trouver l'estampille de la trame 'I' la plus proche (mais postérieure) au début de la portion.
|
||||||
# Trouver l'estampille de la trame 'I' la plus proche (mais antérieure) à la fin de la portion.
|
# Trouver l'estampille de la trame 'I' la plus proche (mais antérieure) à la fin de la portion.
|
||||||
@@ -399,8 +495,10 @@ def main():
|
|||||||
# Si la trame de début est déjà 'I', il n'y a rien à faire (idem pour la fin).
|
# Si la trame de début est déjà 'I', il n'y a rien à faire (idem pour la fin).
|
||||||
# Sinon on extrait les trames 'B' ou 'P' depuis le début jusqu'à la trame 'I' non incluse
|
# Sinon on extrait les trames 'B' ou 'P' depuis le début jusqu'à la trame 'I' non incluse
|
||||||
# Fabriquer une courte vidéo au format MKV reprenant les mêmes codecs que la vidéo originale avec les fichiers extraits précedemment.
|
# Fabriquer une courte vidéo au format MKV reprenant les mêmes codecs que la vidéo originale avec les fichiers extraits précedemment.
|
||||||
|
# mkvmerge() => création d'un fichier part-%d.mkv
|
||||||
|
|
||||||
# Appeler mkvmerge
|
|
||||||
|
# Appeler mkvmerge pour fusionner tous les fichiers part-%d.mkv
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
Reference in New Issue
Block a user