diff options
author | Yves Fischer <yvesf-git@xapek.org> | 2015-12-31 00:07:40 +0100 |
---|---|---|
committer | Yves Fischer <yvesf-git@xapek.org> | 2016-01-08 20:38:18 +0100 |
commit | d0158aa0c5f13ce55f64fa7c3029171d6bfe304f (patch) | |
tree | 5bf2a988dba6e3f87b864d384d9a87213fe23cf1 | |
parent | 48f917e478ed365e58f6880a90dd00be120fcc83 (diff) | |
download | flask-mediabrowser-d0158aa0c5f13ce55f64fa7c3029171d6bfe304f.tar.gz flask-mediabrowser-d0158aa0c5f13ce55f64fa7c3029171d6bfe304f.zip |
encode only UNTIL the next keyframe not INCLUDING
fix handling of last segment (end of file) in m3u8 playlist
remove assumedly unneeded -async 1
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | mediabrowser/__init__.py | 17 | ||||
-rw-r--r-- | mediabrowser/ffmpeg.py | 35 |
3 files changed, 36 insertions, 19 deletions
@@ -9,9 +9,6 @@ This webapplication serves the following purpose: The chunking is done using ffmpeg's `-ss` and `-t` option. This doesn't work properly on some video files. -Also with some files we get chrome errors about audio-splicing with can lead to the point where the browser suddenly -stops playback. - # Compatibility The video stream is encoded as h.264 + AAC stream. Tested with diff --git a/mediabrowser/__init__.py b/mediabrowser/__init__.py index 0bdc21d..855c3e0 100644 --- a/mediabrowser/__init__.py +++ b/mediabrowser/__init__.py @@ -93,10 +93,21 @@ def build(root_directory, cache): def stream(ss, t, path): path = os.path.normpath(path) ospath = os.path.join(root_directory, path) + data = ffprobe(ospath) + duration = float(data['format']['duration']) # cut at next key frame after given time 'ss' - new_ss = ffmpeg.find_next_keyframe(ospath, ss, t / 2) - # find next key frame after given time 't' - new_t = ffmpeg.find_next_keyframe(ospath, ss + t, t / 2) - new_ss + _, new_ss = ffmpeg.find_next_keyframe(ospath, ss, t / 2) + + if ss + t * 2 > duration: + # encode all remain frames at once + new_t = duration - new_ss + else: + # find next key frame after given time 't' + new_t_prev_duration, new_t = ffmpeg.find_next_keyframe(ospath, ss + t, t / 2) + new_t -= new_ss + # minus one frame + new_t -= new_t_prev_duration + process = ffmpeg.stream(ospath, new_ss, new_t) return Response(process.stdout, mimetype='video/MP2T') diff --git a/mediabrowser/ffmpeg.py b/mediabrowser/ffmpeg.py index ba4ee28..841c203 100644 --- a/mediabrowser/ffmpeg.py +++ b/mediabrowser/ffmpeg.py @@ -29,15 +29,15 @@ def ffprobe_data(ospath): def stream(ospath, ss, t): logging.info('start ffmpeg stream h264 480p on path=%s ss=%s t=%s', ospath, ss, t) t_2 = t + 2.0 + output_ts_offset = ss cutter = LoggedPopen( - shlex.split("ffmpeg -ss {ss:.6f} -async 1 -i ".format(**locals())) + + shlex.split("ffmpeg -ss {ss:.6f} -i ".format(**locals())) + [ospath] + shlex.split("-c:a aac -strict experimental -ac 2 -b:a 44k" - "" " -c:v libx264 -pix_fmt yuv420p -profile:v high -level 4.0 -preset ultrafast -trellis 0" " -crf 31 -vf scale=w=trunc(oh*a/2)*2:h=360" - "" - " -f mpegts -output_ts_offset {ss:.6f} -t {t:.6f} pipe:%d.ts".format(**locals())), + " -f mpegts" + " -output_ts_offset {output_ts_offset:.6f} -t {t:.6f} pipe:%d.ts".format(**locals())), stdout=PIPE, stderr=DEVNULL) return cutter @@ -46,41 +46,50 @@ def find_next_keyframe(ospath, start, max_offset): """ :param: start: start search pts as float '123.123' :param: max_offset: max offset after start as float - :return: PTS of next iframe but not search longer than max_offset + :return: (prev_duration, pts): + prev_duration: duration of the frame previous to the found key-frame + pts: PTS of next iframe but not search longer than max_offset + :raise: Exception if no keyframe found """ logging.info("start ffprobe to find next i-frame from {}".format(start)) if start == 0.0: - return 0.0 + logging.info("return (0.0, 0.0) for start == 0.0") + return 0.0, 0.0 process = LoggedPopen( shlex.split("ffprobe -read_intervals {start:.6f}%+{max_offset:.6f} -show_frames " "-select_streams v -print_format flat".format(**locals())) + [ospath], stdout=PIPE, stderr=DEVNULL) data = {'frame': None} + prev_duration = None try: line = process.stdout.readline() while line: frame, name, value = re.match('frames\\.frame\\.(\d+)\\.([^=]*)=(.*)', line.decode('ascii')).groups() if data['frame'] != frame: data.clear() + prev_duration = None data['frame'] = frame data[name] = value - if 'key_frame' in data and data['key_frame'] == '1': + + if 'pkt_duration_time' in data: + prev_duration = float(data['pkt_duration_time'][1:-1]) + + if 'key_frame' in data and data['key_frame'] == '1' and prev_duration is not None: if 'pkt_pts_time' in data and data['pkt_pts_time'][1:-1] != 'N/A' and float( data['pkt_pts_time'][1:-1]) > start: - logging.info("Found pkt_pts_time={}".format(data['pkt_pts_time'])) - return float(data['pkt_pts_time'][1:-1]) + logging.info("Found pkt_pts_time={} prev__duration={}".format(data['pkt_pts_time'], prev_duration)) + return prev_duration, float(data['pkt_pts_time'][1:-1]) elif 'pkt_dts_time' in data and data['pkt_dts_time'][1:-1] != 'N/A' and float( data['pkt_dts_time'][1:-1]) > start: - logging.info("Found pkt_dts_time={}".format(data['pkt_dts_time'])) - return float(data['pkt_dts_time'][1:-1]) + logging.info("Found pkt_dts_time={} prev_duration={}".format(data['pkt_dts_time'], prev_duration)) + return prev_duration, float(data['pkt_dts_time'][1:-1]) line = process.stdout.readline() - raise Exception("Failed to find next i-frame in {} .. {} of {}".format(start, max_offset, ospath)) + raise Exception("Failed to find next i-frame in {} .. {} of {}".format(start, start + max_offset, ospath)) finally: process.stdout.close() process.terminate() - logging.info("finished ffprobe to find next i-frame from {}".format(start)) def calculate_splittimes(ospath, chunk_duration): |