Created
September 18, 2014 14:48
-
-
Save biomancer/8d139177f520b9dd3495 to your computer and use it in GitHub Desktop.
Generating i-frame and byterange m3u8 playlist from .ts video file for usage in HLS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module PlaylistBuilder | |
SEG_DURATION, PKT_SIZE, PKT_POS, PKT_TIME = 0,1,2,3 | |
def self.extract_iframes_data(video_filepath) | |
raise "#{video_filepath} does not exists!" unless File.exists?(video_filepath) | |
iframes_data = [] | |
cmd = "ffprobe -show_frames -select_streams v -of compact -show_entries packet=pts_time,codec_type,pos:frame=pict_type,pkt_pts_time,pkt_size,pkt_pos -i #{video_filepath.shellescape}" | |
frames_and_packets = nil | |
r = Benchmark.measure('') do | |
frames_and_packets = `#{cmd}`.split("\n") | |
end | |
puts r | |
iframes = frames_and_packets.grep(/pict_type=I/) | |
next_pkt_pts_time = nil | |
iframes.each_with_index do |iframe, index| | |
pkt_pts_time = next_pkt_pts_time || iframe.match(/.*pkt_pts_time=([0-9]*.[0-9]*).*/)[1] | |
if index >= iframes.count - 1 | |
next_pkt_pts_time = frames_and_packets.last.match(/.*pkt_pts_time=([0-9]*.[0-9]*).*/)[1] | |
else | |
next_pkt_pts_time = iframes[index + 1].match(/.*pkt_pts_time=([0-9]*.[0-9]*).*/)[1] | |
end | |
pkt_size = iframe.match(/.*pkt_size=([0-9]*).*/)[1] | |
pkt_pos = iframe.match(/.*pkt_pos=([0-9]*).*/)[1] | |
segment_duration = (next_pkt_pts_time.to_f - pkt_pts_time.to_f).round(5) | |
iframes_data << [segment_duration, pkt_size.to_i, pkt_pos.to_i, pkt_pts_time.to_f.round(5)] | |
end | |
packets_pos = frames_and_packets.map{|p| p.match(/^packet.*pos=([0-9]*).*/).try{|m| m[1].to_i}}.compact | |
packets_pos_reversed = {} | |
packets_pos.each_with_index do |pos, i| | |
packets_pos_reversed[pos] = i | |
end | |
iframes_data.each do |iframe_data| | |
iframe_size = nil | |
if packets_pos_reversed[iframe_data[PKT_POS]] && packets_pos[packets_pos_reversed[iframe_data[PKT_POS]] + 1] | |
# https://github.com/pbs/iframe-playlist-generator/blob/master/iframeplaylistgenerator/generator.py | |
# "We compared the output of our library to Apple's example streams, and were off by 188 bytes | |
# for each I-frame byte-range." | |
# biomancer: Even with this fix result is sometimes off by extra 188 (188*2 total), for random chunks, | |
# I have not found any correlation with other packet parameters | |
iframe_size = packets_pos[packets_pos_reversed[iframe_data[PKT_POS]] + 1] - iframe_data[PKT_POS] + 188 | |
end | |
iframe_data[PKT_SIZE] = iframe_size if iframe_size | |
end | |
iframes_data | |
end | |
def self.build_byterange_playlist(iframes_data, filesize, options = {}) | |
options[:target_duration] ||= 10 | |
playlist = <<-EOS_TOKEN | |
#EXTM3U | |
#EXT-X-TARGETDURATION:#{options[:target_duration]} | |
#EXT-X-VERSION:4 | |
#EXT-X-MEDIA-SEQUENCE:0 | |
#EXT-X-PLAYLIST-TYPE:VOD | |
EOS_TOKEN | |
last_iframe = [0,0,0,0] | |
iframes_data.each_with_index do |iframe_data, index| | |
duration_from_last_iframe = iframe_data[PKT_TIME] - last_iframe[PKT_TIME] | |
# playlist += "\n#DEBUG: duration_from_last_iframe: #{duration_from_last_iframe}\n" | |
if !iframes_data[index+1] | |
#last part | |
if duration_from_last_iframe + iframe_data[SEG_DURATION] > options[:target_duration] | |
playlist += byterange_chunk( | |
duration_from_last_iframe.round(5), | |
iframe_data[PKT_POS] - last_iframe[PKT_POS], | |
last_iframe[PKT_POS], | |
options[:url_filename] | |
) | |
last_iframe = iframe_data | |
duration_from_last_iframe = 0 | |
end | |
playlist += byterange_chunk( | |
(duration_from_last_iframe + iframe_data[SEG_DURATION]).round(5), | |
filesize - last_iframe[PKT_POS], | |
last_iframe[PKT_POS], | |
options[:url_filename] | |
) | |
elsif iframes_data[index+1][PKT_TIME] - last_iframe[PKT_TIME] >= options[:target_duration] | |
#first or mid part | |
playlist += byterange_chunk( | |
duration_from_last_iframe.round(5), | |
iframe_data[PKT_POS] - last_iframe[PKT_POS], | |
last_iframe[PKT_POS], | |
options[:url_filename] | |
) | |
last_iframe = iframe_data | |
end | |
end | |
playlist += "#EXT-X-ENDLIST\n" | |
end | |
def self.build_iframe_playlist(iframes_data, options = {}) | |
options[:target_duration] ||= iframes_data.map(&:first).max.ceil | |
playlist = <<-EOS_TOKEN | |
#EXTM3U | |
#EXT-X-TARGETDURATION:#{options[:target_duration]} | |
#EXT-X-VERSION:4 | |
#EXT-X-MEDIA-SEQUENCE:0 | |
#EXT-X-PLAYLIST-TYPE:VOD | |
#EXT-X-I-FRAMES-ONLY | |
EOS_TOKEN | |
iframes_data.each do |iframe_data| | |
playlist += byterange_chunk( | |
iframe_data[SEG_DURATION], | |
iframe_data[PKT_SIZE], | |
iframe_data[PKT_POS], | |
options[:url_filename] | |
) | |
end | |
playlist += "#EXT-X-ENDLIST\n" | |
end | |
def self.byterange_chunk(duration, size, pos, file) | |
<<-EOS_TOKEN | |
#EXTINF:#{duration}, | |
#EXT-X-BYTERANGE:#{size}@#{pos} | |
#{file} | |
EOS_TOKEN | |
end | |
def self.build_byterange_playlist_from_file(video_filepath, output_filepath, options = {}) | |
iframes_data = extract_iframes_data(video_filepath) | |
options[:url_filename] ||= File.basename video_filepath | |
playlist = build_byterange_playlist(iframes_data, File.size(video_filepath), options) | |
File.open(output_filepath, 'w') {|f| f.write(playlist) } | |
end | |
def self.build_iframe_playlist_from_file(video_filepath, output_filepath, options = {}) | |
iframes_data = extract_iframes_data(video_filepath) | |
options[:url_filename] ||= File.basename video_filepath | |
playlist = build_iframe_playlist(iframes_data, options) | |
File.open(output_filepath, 'w') {|f| f.write(playlist) } | |
end | |
end |
Usage example:
PlaylistBuilder.build_byterange_playlist_from_file('/home/me/somevideo.ts', '/home/me/byterange_playlist.m3u8', {target_duration: 7})
PlaylistBuilder.build_iframe_playlist_from_file('/home/me/somevideo.ts', '/home/me/iframe_playlist.m3u8', {target_duration: 7})
how about getting validity of an online file ? uri needed.. is that possible ?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Result of http://stackoverflow.com/questions/23497782/how-to-create-byte-range-m3u8-playlist-for-hls