I am trying to create a media server, and only want to transcode video when it's played. However I need the playlist in advance so that the client play can load the video metadata. Is this possible?
I want to do something like this:
Client -> GET m3u8 (pregenerated in advance)
Client -> GET ts -> Transcode only this single ts file
Client -> GET ts -> Transcode only this single ts file
Client -> GET ts -> Transcode only this single ts file
I don't want to transcode the entire video at once, I want to be able to only transcode the part that is requested.
Is this possible? Also open to using MPEG-DASH instead if needed.
before running ffmpeg, using the requested segement number to generate start seconds and put it after the '-ss' to ffmpeg command options, and you must specify the '-r' option to match your hls_time and fps for the final ts segement time matching your predefined segment duration.Or you will get wrong ts duration for below
root@YOLE-UJIK:~/tran_6# cat generated_by_ffmpeg.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:5
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:5.005000,
stream0_0.ts
#EXTINF:5.005000,
stream0_1.ts
#EXTINF:5.005000,
stream0_2.ts
#EXTINF:5.005000,
stream0_3.ts
#EXTINF:5.005000,
stream0_4.ts
#EXTINF:5.005000,
stream0_5.ts
#EXTINF:4.971633,
stream0_6.ts
#EXTINF:5.005000,
stream0_7.ts
I was using golang as hls serve code language, my segment duration was predefined as 5s
I generate the m3u8 part using video total duration and my segment duation options, the code below show how to write the preserve m3u8:
func BuildMainPlaylistTranscoding(opt Sw, segmentDuration float64) string {
segmentDurationsSeconds := []float64{}
totalDuration := float64(opt.Duration)
standardPartSegDuration := segmentDuration
for ; totalDuration > 0; totalDuration -= standardPartSegDuration {
if totalDuration > standardPartSegDuration {
segmentDurationsSeconds = append(segmentDurationsSeconds, standardPartSegDuration)
} else {
segmentDurationsSeconds = append(segmentDurationsSeconds, totalDuration)
}
}
buildder := strings.Builder{}
buildder.WriteString(fmt.Sprintf(`#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:%d
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD`, 6))
// apple must end with .ts, otherwise safari will not request ts data
for index := 0; index < len(segmentDurationsSeconds); index++ {
buildder.WriteString(fmt.Sprintf("\n#EXTINF:%.3f,\nstream0_%d.ts", segmentDurationsSeconds[index], index))
}
buildder.WriteString(fmt.Sprintf("\n#EXT-X-ENDLIST"))
return buildder.String()
}
When the media segement http request arrived, i'm using the ffmpeg command below to generate ts segment and serve.
ffmpeg -hide_banner -analyzeduration 200M -init_hw_device vaapi=va:/dev/dri/renderD128 -filter_hw_device va -hwaccel vaapi -hwaccel_output_format vaapi -i "/data/user/admin/Downloads/迪迦奥特曼 EP48 -月球来的逃亡者1080P.mkv" -copyts -autoscale 0 -map_metadata -1 -map_chapters -1 -threads 0 -codec:v:0 h264_vaapi -b:v 1000000 -r 30 -force_key_frames "expr:gte(t,n_forced*5)" -filter_complex "[0:3]scale=s=960x720:flags=fast_bilinear[sub];[0:0]setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709,scale_vaapi=w=960:h=720:format=nv12:extra_hw_frames=24,hwdownload,format=nv12[main];[main][sub]overlay=eof_action=pass:shortest=1:repeatlast=0,hwupload_vaapi" -start_at_zero -codec:a:0 aac -ac 2 -ab 128000 -max_muxing_queue_size 2048 -f hls -start_number 0 -hls_time 5 -hls_segment_type mpegts -hls_list_size 0 -hls_segment_filename "/root/tran_7/stream0_%d.ts" -hls_playlist_type vod "/root/tran_7/generated_by_ffmpeg.m3u8"
The video ffprobe data was below
"streams": [
{
"index": 0,
"codec_name": "hevc",
"codec_long_name": "H.265 / HEVC (High Efficiency Video Coding)",
"profile": "Main 10",
"codec_type": "video",
"codec_tag_string": "[0][0][0][0]",
"codec_tag": "0x0000",
"width": 1440,
"height": 1080,
"coded_width": 1440,
"coded_height": 1080,
"closed_captions": 0,
"film_grain": 0,
"has_b_frames": 2,
"sample_aspect_ratio": "1:1",
"display_aspect_ratio": "4:3",
"pix_fmt": "yuv420p10le",
"level": 120,
"color_range": "tv",
"color_space": "bt709",
"chroma_location": "left",
"refs": 1,
"r_frame_rate": "30000/1001",
"avg_frame_rate": "30000/1001",
"time_base": "1/1000",
"start_pts": 0,
"start_time": "0.000000",
"extradata": "\n00000000: 0102 2000 0000 9000 0000 0000 78f0 00fc .. .........x...\n00000010: fdfa fa00 0047 0420 0001 0019 4001 0c01 .....G. ....@...\n00000020: ffff 0220 0000 0300 9000 0003 0000 0300 ... ............\n00000030: 7895 8802 4021 0001 002c 4201 0102 2000 x...@!...,B... .\n00000040: 0003 0090 0000 0300 0003 0078 a002 d080 ...........x....\n00000050: 10e4 d965 622e 4c2a 6a04 0402 0800 001f ...eb.L*j.......\n00000060: 4800 03a9 8040 2200 0100 0844 01c1 7329 H....@\"....D..s)\n00000070: 59c6 c927 0001 00a4 4e01 059f 2ca2 de09 Y..'....N...,...\n00000080: b517 47db bb55 a4fe 7fc2 fc4e 7832 3635 ..G..U.....Nx265\n00000090: 202d 202d 2048 2e32 3635 2f48 4556 4320 - - H.265/HEVC \n000000a0: 636f 6465 6320 2d20 436f 7079 7269 6768 codec - Copyrigh\n000000b0: 7420 3230 3133 2d32 3031 3637 2028 6329 t 2013-20167 (c)\n000000c0: 204d 756c 7469 636f 7265 7761 7265 2c20 Multicoreware, \n000000d0: 496e 6320 2d20 6874 7470 3a2f 2f78 3236 Inc - http://x26\n000000e0: 352e 6f72 6720 2d20 6f70 7469 6f6e 733a 5.org - options:\n000000f0: 2063 7075 6964 3d31 3137 3335 3033 2066 cpuid=1173503 f\n00000100: 7261 6d65 2d74 6872 6561 6473 3d33 206e rame-threads=3 n\n00000110: 756d 612d 706f 6f6c 733d 3880 uma-pools=8.\n",
"extradata_size": 284,
"disposition": {
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 1,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0,
"captions": 0,
"descriptions": 0,
"metadata": 0,
"dependent": 0,
"still_image": 0
},
"tags": {
"language": "jpn",
"BPS": "4652198",
"BPS-eng": "4652198",
"DURATION": "00:24:27.466000000",
"DURATION-eng": "00:24:27.466000000",
"NUMBER_OF_FRAMES": "43980",
"NUMBER_OF_FRAMES-eng": "43980",
"NUMBER_OF_BYTES": "853367844",
"NUMBER_OF_BYTES-eng": "853367844",
"_STATISTICS_WRITING_APP": "mkvmerge v8.5.2 ('Crosses') 64bit",
"_STATISTICS_WRITING_APP-eng": "mkvmerge v8.5.2 ('Crosses') 64bit",
"_STATISTICS_WRITING_DATE_UTC": "2017-08-13 13:20:32",
"_STATISTICS_WRITING_DATE_UTC-eng": "2017-08-13 13:20:32",
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES",
"_STATISTICS_TAGS-eng": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
}
},
{
"index": 1,
"codec_name": "ac3",
"codec_long_name": "ATSC A/52A (AC-3)",
"codec_type": "audio",
"codec_tag_string": "[0][0][0][0]",
"codec_tag": "0x0000",
"sample_fmt": "fltp",
"sample_rate": "48000",
"channels": 2,
"channel_layout": "stereo",
"bits_per_sample": 0,
"initial_padding": 0,
"r_frame_rate": "0/0",
"avg_frame_rate": "0/0",
"time_base": "1/1000",
"start_pts": 0,
"start_time": "0.000000",
"bit_rate": "384000",
"extradata": "\n",
"disposition": {
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 1,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0,
"captions": 0,
"descriptions": 0,
"metadata": 0,
"dependent": 0,
"still_image": 0
},
"tags": {
"language": "chi",
"BPS": "384000",
"BPS-eng": "384000",
"DURATION": "00:24:27.456000000",
"DURATION-eng": "00:24:27.456000000",
"NUMBER_OF_FRAMES": "45858",
"NUMBER_OF_FRAMES-eng": "45858",
"NUMBER_OF_BYTES": "70437888",
"NUMBER_OF_BYTES-eng": "70437888",
"_STATISTICS_WRITING_APP": "mkvmerge v8.5.2 ('Crosses') 64bit",
"_STATISTICS_WRITING_APP-eng": "mkvmerge v8.5.2 ('Crosses') 64bit",
"_STATISTICS_WRITING_DATE_UTC": "2017-08-13 13:20:32",
"_STATISTICS_WRITING_DATE_UTC-eng": "2017-08-13 13:20:32",
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES",
"_STATISTICS_TAGS-eng": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
}
},
{
"index": 2,
"codec_name": "flac",
"codec_long_name": "FLAC (Free Lossless Audio Codec)",
"codec_type": "audio",
"codec_tag_string": "[0][0][0][0]",
"codec_tag": "0x0000",
"sample_fmt": "s16",
"sample_rate": "48000",
"channels": 2,
"channel_layout": "stereo",
"bits_per_sample": 0,
"initial_padding": 0,
"r_frame_rate": "0/0",
"avg_frame_rate": "0/0",
"time_base": "1/1000",
"start_pts": 0,
"start_time": "0.000000",
"bits_per_raw_sample": "16",
"extradata": "\n00000000: 1000 1000 0000 1000 2ec2 0bb8 02f0 0432 ...............2\n00000010: cea0 06c0 50fc 36d6 8e6d fb18 551a c62e ....P.6..m..U...\n00000020: ac3e .>\n",
"extradata_size": 34,
"disposition": {
"default": 0,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 0,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0,
"captions": 0,
"descriptions": 0,
"metadata": 0,
"dependent": 0,
"still_image": 0
},
"tags": {
"language": "jpn",
"BPS": "662320",
"BPS-eng": "662320",
"DURATION": "00:24:27.470000000",
"DURATION-eng": "00:24:27.470000000",
"NUMBER_OF_FRAMES": "17197",
"NUMBER_OF_FRAMES-eng": "17197",
"NUMBER_OF_BYTES": "121492010",
"NUMBER_OF_BYTES-eng": "121492010",
"_STATISTICS_WRITING_APP": "mkvmerge v8.5.2 ('Crosses') 64bit",
"_STATISTICS_WRITING_APP-eng": "mkvmerge v8.5.2 ('Crosses') 64bit",
"_STATISTICS_WRITING_DATE_UTC": "2017-08-13 13:20:32",
"_STATISTICS_WRITING_DATE_UTC-eng": "2017-08-13 13:20:32",
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES",
"_STATISTICS_TAGS-eng": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
}
},
{
"index": 3,
"codec_name": "hdmv_pgs_subtitle",
"codec_long_name": "HDMV Presentation Graphic Stream subtitles",
"codec_type": "subtitle",
"codec_tag_string": "[0][0][0][0]",
"codec_tag": "0x0000",
"r_frame_rate": "0/0",
"avg_frame_rate": "0/0",
"time_base": "1/1000",
"start_pts": 0,
"start_time": "0.000000",
"duration_ts": 1467470,
"duration": "1467.470000",
"extradata": "\n",
"disposition": {
"default": 1,
"dub": 0,
"original": 0,
"comment": 0,
"lyrics": 0,
"karaoke": 0,
"forced": 1,
"hearing_impaired": 0,
"visual_impaired": 0,
"clean_effects": 0,
"attached_pic": 0,
"timed_thumbnails": 0,
"captions": 0,
"descriptions": 0,
"metadata": 0,
"dependent": 0,
"still_image": 0
},
"tags": {
"language": "chi",
"BPS": "10374",
"BPS-eng": "10374",
"DURATION": "00:23:32.111000000",
"DURATION-eng": "00:23:32.111000000",
"NUMBER_OF_FRAMES": "564",
"NUMBER_OF_FRAMES-eng": "564",
"NUMBER_OF_BYTES": "1831328",
"NUMBER_OF_BYTES-eng": "1831328",
"_STATISTICS_WRITING_APP": "mkvmerge v8.5.2 ('Crosses') 64bit",
"_STATISTICS_WRITING_APP-eng": "mkvmerge v8.5.2 ('Crosses') 64bit",
"_STATISTICS_WRITING_DATE_UTC": "2017-08-13 13:20:32",
"_STATISTICS_WRITING_DATE_UTC-eng": "2017-08-13 13:20:32",
"_STATISTICS_TAGS": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES",
"_STATISTICS_TAGS-eng": "BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES"
}
}
],
"format": {
"filename": "/data/user/admin/Downloads/迪迦奥特曼 EP48 -月球来的逃亡者1080P.mkv",
"nb_streams": 4,
"nb_programs": 0,
"format_name": "matroska,webm",
"format_long_name": "Matroska / WebM",
"start_time": "0.000000",
"duration": "1467.470000",
"size": "1047593097",
"bit_rate": "5711016",
"probe_score": 100,
"tags": {
"encoder": "libebml v1.3.3 + libmatroska v1.4.4",
"creation_time": "2017-08-13T13:20:32.000000Z"
}
}
}