├── .github └── workflows │ └── docker-image.yml ├── Dockerfile ├── LICENSE ├── README.md ├── _config.yml ├── src ├── fluid-player.html ├── htaccess ├── index.html ├── plyr.html ├── videogular.html └── videojs.html ├── transcode.sh └── webvtt.sh /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ latest, edge, jpg-thumbnails, libressl, vtt-preview-generator, webvtt-mt ] 7 | 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | 17 | - name: Set up QEMU 18 | uses: docker/setup-qemu-action@v1 19 | 20 | - name: Set up Docker Buildx 21 | uses: docker/setup-buildx-action@v1 22 | 23 | - name: Login to DockerHub 24 | if: github.event_name != 'pull_request' 25 | uses: docker/login-action@v1 26 | with: 27 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 28 | password: ${{ secrets.DOCKER_HUB_TOKEN }} 29 | 30 | - name: Login to GitHub Container Registry 31 | if: github.event_name != 'pull_request' 32 | uses: docker/login-action@v1 33 | with: 34 | registry: ghcr.io 35 | username: ${{ github.repository_owner }} 36 | password: ${{ secrets.GHCR_TOKEN }} 37 | 38 | - name: Build and push 39 | id: docker_build 40 | uses: docker/build-push-action@v2 41 | with: 42 | push: ${{ github.event_name != 'pull_request' }} 43 | tags: | 44 | ${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:${{ github.ref_name }} 45 | ghcr.io/${{ secrets.DOCKER_HUB_USERNAME }}/${{ secrets.DOCKER_HUB_REPOSITORY }}:${{ github.ref_name }} 46 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | COPY ./transcode.sh /bin/transcode.sh 4 | COPY ./webvtt.sh /bin/webvtt.sh 5 | 6 | RUN buildDeps="alsa-lib-dev \ 7 | build-base \ 8 | brotli-static \ 9 | bzip2-static \ 10 | coreutils \ 11 | expat-dev \ 12 | faad2-static \ 13 | ffmpeg-dev \ 14 | freetype-static \ 15 | freetype-dev \ 16 | git \ 17 | glu-dev \ 18 | jack-dev \ 19 | jpeg-dev \ 20 | lame-dev \ 21 | libass-dev \ 22 | libjpeg-turbo-dev \ 23 | libmad-dev \ 24 | libogg-dev \ 25 | libpng-dev \ 26 | libpng-static \ 27 | libpulse \ 28 | libtheora-dev \ 29 | libvorbis-dev \ 30 | libvpx-dev \ 31 | libwebp-dev \ 32 | libxv-dev \ 33 | mesa-dev \ 34 | mesa-utils \ 35 | musl-dev \ 36 | openjpeg-dev \ 37 | openssl-dev \ 38 | opus-dev \ 39 | sdl2-dev \ 40 | x264-dev \ 41 | x265-dev \ 42 | xvidcore-static \ 43 | yasm-dev \ 44 | zlib-static" && \ 45 | apk add --no-cache --update ${buildDeps} bash exiv2 ffmpeg libpng libxslt openssl && \ 46 | git clone https://github.com/squidpickles/mpd-to-m3u8.git /app/mpd-to-m3u8 && \ 47 | rm -rf !$/.git && \ 48 | git clone https://github.com/gpac/gpac.git /tmp/gpac && \ 49 | cd /tmp/gpac && ./configure --static-bin && make -j4 && make install && make distclean && cd && rm -rf /tmp/gpac && \ 50 | apk del ${buildDeps} && rm -rf /var/cache/apk/* && \ 51 | chmod +x /bin/transcode.sh && \ 52 | chmod +x /bin/webvtt.sh 53 | 54 | COPY ./src /app/src 55 | 56 | WORKDIR /video 57 | ENTRYPOINT ["/bin/transcode.sh"] 58 | CMD ["*.mkv"] 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 majamee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Alpine Dash HLS Logo

2 | 3 | [![Docker Image CI](https://github.com/majamee/alpine-dash-hls/actions/workflows/docker-image.yml/badge.svg)](https://github.com/majamee/alpine-dash-hls/actions/workflows/docker-image.yml) | 4 | [![](https://img.shields.io/docker/stars/majamee/alpine-dash-hls.svg?style=social)](https://hub.docker.com/r/majamee/alpine-dash-hls/?target=_blank) [![](https://img.shields.io/docker/pulls/majamee/alpine-dash-hls.svg?style=social)](https://hub.docker.com/r/majamee/alpine-dash-hls/?target=_blank) | [![Github Repo Stars](https://img.shields.io/github/stars/majamee/alpine-dash-hls?style=social)](https://github.com/majamee/alpine-dash-hls/?target=_blank) 5 | 6 | 7 | # [Alpine Dash HLS](https://majamee.de) 8 | A ready-prepared video transcoding pipeline to create DASH/ HLS compatible video files & playlists. 9 | 10 | Recommended usage via Docker [Docker Desktop](https://www.docker.com/products/docker-desktop/?target=_blank) & [Docker Hub](https://hub.docker.com/r/majamee/alpine-dash-hls/?target=_blank). 11 | 12 | # Simplified usage (run in shell/ terminal/ cmd) 13 | Prerequisite: [Docker](https://www.docker.com/?target=_blank) needs to be installed and running. 14 | ```sh 15 | docker pull majamee/alpine-dash-hls 16 | docker run -v /absolute/path/to/video/:/video majamee/alpine-dash-hls name_of_my_video_file.ext 17 | ``` 18 | Please just replace in the command above the absolute path to your video file folder and the full file name of your video file to be converted. 19 | You can also use [tags](https://hub.docker.com/r/majamee/alpine-dash-hls/tags/) like `majamee/alpine-dash-hls:edge` (e.g. uses [alpine](https://hub.docker.com/_/alpine/)'s edge version as base). 20 | 21 | There is also a parameter `--transcode-only` which you can append to the `docker run` command, in order to skip the html and image file output. 22 | Example: 23 | ```sh 24 | docker pull majamee/alpine-dash-hls 25 | docker run -v /absolute/path/to/video/:/video majamee/alpine-dash-hls name_of_my_video_file.ext --transcode-only 26 | ``` 27 | 28 | ## Examplary toolchain usage 29 | (Based on work of [squidpickles](https://github.com/squidpickles?target=_blank)) 30 | 31 | Just use Kitematic to open the shared folder, place your video file in there, replace `"input.mkv"` in the commands below by your input video file (without `""`) and execute the shell commands subsequent into the Docker container. 32 | ```sh 33 | # 1080p@CRF22 34 | ffmpeg -y -threads 0 -i "input.mkv" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -profile:v high -level 4.0 -vf "scale=min'(1920,iw)':-4" -crf 22 -movflags faststart -write_tmcd 0 intermed_1080p.mp4 35 | # 720p@CRF22 36 | ffmpeg -y -threads 0 -i "input.mkv" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -profile:v high -level 4.0 -vf "scale=min'(1280,iw)':-4" -crf 22 -movflags faststart -write_tmcd 0 intermed_720p.mp4 37 | # 480p@CRF22 38 | ffmpeg -y -threads 0 -i "input.mkv" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -profile:v high -level 4.0 -vf "scale=min'(720,iw)':-4" -crf 22 -movflags faststart -write_tmcd 0 intermed_480p.mp4 39 | # 128k AAC audio only 40 | ffmpeg -y -threads 0 -i "input.mkv" -vn -c:a aac -b:a 128k audio_128k.m4a 41 | 42 | # Create MPEG-DASH files (segments & mpd-playlist) 43 | MP4Box -dash 2000 -rap -frag-rap -url-template -dash-profile onDemand -segment-name 'segment_$RepresentationID$' -out playlist.mpd intermed_1080p.mp4 intermed_720p.mp4 intermed_480p.mp4 audio_128k.m4a 44 | 45 | # Create HLS playlists for each quality level 46 | ffmpeg -y -threads 0 -i intermed_1080p.mp4 -i audio_128k.m4a -map 0:v:0 -map 1:a:0 -shortest -acodec copy -vcodec copy -hls_time 2 -hls_list_size 0 -hls_flags single_file segment_1.m3u8 47 | ffmpeg -y -threads 0 -i intermed_720p.mp4 -i audio_128k.m4a -map 0:v:0 -map 1:a:0 -shortest -acodec copy -vcodec copy -hls_time 2 -hls_list_size 0 -hls_flags single_file segment_2.m3u8 48 | ffmpeg -y -threads 0 -i intermed_480p.mp4 -i audio_128k.m4a -map 0:v:0 -map 1:a:0 -shortest -acodec copy -vcodec copy -hls_time 2 -hls_list_size 0 -hls_flags single_file segment_3.m3u8 49 | ffmpeg -y -threads 0 -i audio_128k.m4a -acodec copy -vcodec copy -hls_time 2 -hls_list_size 0 -hls_flags single_file segment_4.m3u8 50 | 51 | # Transform MPD-Master-Playlist to M3U8-Master-Playlist 52 | xsltproc --stringparam run_id "segment" /app/mpd-to-m3u8/mpd_to_hls.xsl playlist.mpd > playlist.m3u8 53 | ``` 54 | 55 | I am glad to receive any improvement ideas about this "any video to DASH/ HLS" pipeline. 56 | Especially if someone has any input on integrating better [Apple's support of fragemented mp4 (fmp4) files](https://gpac.wp.imt.fr/tag/hls-fmp4/) in this pipeline. 57 | 58 | Suggestions welcome. :) 59 | 60 | ## General hints for hosting the files (to test streaming) 61 | * Video and playlist files should be hosted best via HTTPS 62 | * DASH requires the .mpd playlist to be set as `Content-Type: application/dash+xml` 63 | * No specific streaming server is required, but your hosting should have progressive downloading enabled 64 | * If using a different domain name for the video files compared to the page where the player is hosted CORS headers need to be set 65 | 66 | ## Tools to test the generated files for streaming 67 | * HLS (e.g. Safari on Mac OS X): https://videojs.github.io/videojs-contrib-hls/ (use the .m3u8 master-playlist) 68 | * DASH (e.g. Firefox/ Chrome): https://reference.dashif.org/dash.js/ (use the latest released version & the .mpd playlist) 69 | 70 | # Features 71 | * Supported devices: iOS (Chrome/ Firefox/ Safari), Android (Chrome/ Firefox), Mac (Chrome/ Firefox/ Safari), Windows (Chrome/ Firefox/ EDGE) 72 | * Creates DASH (VOD) compatible files (including Safari on Mac) 73 | * Creates HLS files for compatibility with Safari on iOS 74 | * Optimizes video files for web playback (`moov` atom) 75 | * Compresses videos using H.264@CRF22 (for best compatibility) 76 | * Compresses audio using AAC@128k (for DASH as separate track to save data) 77 | * Creates automatically 3 quality levels (Full HD/ HD/ DVD quality) 78 | * Fragments video files in 2 second windows to allow dynamic quality switching based on available bandwidth 79 | * Creates master MPD-Playlist which connects everything (MPEG-DASH) 80 | * Creates master M3U8-Playlist for HLS 81 | * Creates all output files neatly stored in a sub-folder matching the video file name in the folder `output` next to the transcoded video file 82 | * Adds also HTML and `.htaccess` file including code ready for inclusion into the own website for playback next to all other created files 83 | * Generates and sets Poster image (from second 3 of the input video file) 84 | * Generates and includes video preview thumbnails (currently only natively supported by [Fluid Player](https://www.fluidplayer.com/?target=_blank) via [WebVTT](http://www.webvtt.cc/?target=_blank)) 85 | * Video preview support thumbnail support added for [Video.js](https://videojs.com/?target=_blank) via [videojs-vtt-thumbnails](https://github.com/chrisboustead/videojs-vtt-thumbnails/) 86 | * Included fallback player (`fluid-player.html`) is based on the great work of the devs at [Fluid Player](https://www.fluidplayer.com/?target=_blank) 87 | * Included second fallback player (`plyr.html`) is based on the great work of the devs at [Plyr](https://plyr.io/?target=_blank) 88 | * Included third fallback player (`videogular.html`) is based on the great work of the devs at [Videogular](http://www.videogular.com/?target=_blank) 89 | * Included player (`index.html`) is based on the great work of the guys at [Video.js](https://videojs.com/?target=_blank) 90 | 91 | # Tip 92 | For creating DASH/ HLS compatible files for multiple videos in a single run, please have a look at: 93 | * [https://majamee.de/auto-dash-hls](https://majamee.de/auto-dash-hls/?target=_blank) 94 | 95 | # Demo 96 | [https://majamee.de/demos](https://majamee.de/demos/?target=_blank) 97 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /src/fluid-player.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Auto-DASH-HLS Fluid-Player 13 | 14 | 15 | 16 |
17 | 26 |
27 | 28 | 50 | 51 | -------------------------------------------------------------------------------- /src/htaccess: -------------------------------------------------------------------------------- 1 | # This adds the MIME Types for the different file types used 2 | AddType application/dash+xml .mpd 3 | AddType application/x-mpegURL .m3u8 4 | AddType video/MP2T .ts 5 | AddType video/mp4 .mp4 6 | 7 | # If the video files are hosted on a different location than the player CORS headers 8 | # need to be set to allow browsers access to the resources 9 | # You might want to replace the * with your players origination host so not every page 10 | # can include your hosted content freely, but * is the best for testing the general setup 11 | # as it invokes the least restrictions 12 | 13 | Header set Access-Control-Allow-Origin * 14 | Header set Access-Control-Allow-Credentials true 15 | Header set Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept, Range" 16 | Header set Access-Control-Allow-Methods "GET, POST, OPTIONS, HEAD" 17 | 18 | 19 | # Grant access for files 20 | 21 | # Apache < 2.3 22 | 23 | Order allow,deny 24 | Allow from all 25 | Satisfy All 26 | 27 | # Apache ≥ 2.3 28 | 29 | Require all granted 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | fluid-player.html -------------------------------------------------------------------------------- /src/plyr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Auto-DASH-HLS Plyr Player 20 | 21 | 22 | 23 |
24 | 31 |
32 | 33 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/videogular.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Auto-DASH-HLS Videogular Player 19 | 20 | 21 | 22 |
23 | 24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | {{ currentTime | date:'mm:ss':'+0000' }} 32 | 33 | 34 | 35 | {{ timeLeft | date:'mm:ss':'+0000' }} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |

46 | To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video. 47 |

48 |
49 |
50 |
51 | 52 |
53 | 54 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/videojs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Auto-DASH-HLS VideoJS Player 19 | 20 | 21 | 22 |
23 | 33 |
34 | 35 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /transcode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | input_file="${1?Input file missing}" 4 | # directoryname=$(dirname "${input_file}") 5 | filename=$(basename "${input_file}") 6 | filename="${filename%.*}" 7 | frames=$(ffprobe -v error -select_streams v:0 -show_entries stream=nb_frames -of default=nokey=1:noprint_wrappers=1 "${input_file}") 8 | 9 | echo -e "\nCurrent video: ${input_file}\nDetected file name: ${filename}\nTotal # of frames: ${frames}"; 10 | mkdir -p "output/${filename}/"; 11 | 12 | if [[ -z "$2" ]]; then 13 | # Create Video Preview thumbnails, unless parameter "--transcode-only" 14 | /bin/webvtt.sh "${input_file}"; 15 | 16 | # Create Video Poster (from second 3), unless parameter "--transcode-only" 17 | echo -e "\nCreating Video Poster (from second 3)" && \ 18 | ffmpeg -y -v error -i "${input_file}" -ss 00:00:03 -vframes 1 -vcodec png "output/${filename}/thumbnails/poster.png"; 19 | else 20 | if [ $2 != "--transcode-only" ]; then 21 | # Create Video Preview thumbnails, unless parameter "--transcode-only" 22 | /bin/webvtt.sh "${input_file}"; 23 | 24 | # Create Video Poster (from second 3), unless parameter "--transcode-only" 25 | echo -e "\nCreating Video Poster (from second 3)" && \ 26 | ffmpeg -y -v error -i "${input_file}" -ss 00:00:03 -vframes 1 -vcodec png "output/${filename}/thumbnails/poster.png"; 27 | else 28 | echo -e "\nTranscode only selected: No HTML and image files will be created."; 29 | fi 30 | fi 31 | 32 | # Transcode the video 33 | echo -e "\nCreating MPEG-DASH files" && \ 34 | # 1080p@CRF22 35 | echo -e "Total # of frames: ${frames}\n\nCreating Full HD version (no upscaling, Step 1/4)" && \ 36 | ffmpeg -y -threads 0 -v error -stats -i "${input_file}" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -profile:v high -level 4.0 -vf "scale=min'(1920,iw)':-4" -crf 22 -movflags faststart -write_tmcd 0 "output/${filename}/intermed_1080p.mp4" && \ 37 | # 720p@CRF22 38 | echo -e "Creating HD version (no upscaling, Step 2/4)" && \ 39 | ffmpeg -y -threads 0 -v error -stats -i "${input_file}" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -profile:v high -level 4.0 -vf "scale=min'(1280,iw)':-4" -crf 22 -movflags faststart -write_tmcd 0 "output/${filename}/intermed_720p.mp4" && \ 40 | # 480p@CRF22 41 | echo -e "Creating DVD quality version (no upscaling, Step 3/4)" && \ 42 | ffmpeg -y -threads 0 -v error -stats -i "${input_file}" -an -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -profile:v high -level 4.0 -vf "scale=min'(720,iw)':-4" -crf 22 -movflags faststart -write_tmcd 0 "output/${filename}/intermed_480p.mp4" && \ 43 | # 128k AAC audio only 44 | echo -e "Creating audio only version (Step 4/4)" && \ 45 | ffmpeg -y -threads 0 -v error -stats -i "${input_file}" -vn -c:a aac -b:a 128k "output/${filename}/audio_128k.m4a" && \ 46 | 47 | # Create MPEG-DASH files (segments & mpd-playlist) 48 | echo -e "\nCreating MPEG-DASH files & MPD-playlist" && \ 49 | MP4Box -dash 2000 -rap -frag-rap -url-template -dash-profile onDemand -segment-name 'segment_$RepresentationID$' -out "output/${filename}/playlist.mpd" "output/${filename}/intermed_1080p.mp4" "output/${filename}/intermed_720p.mp4" "output/${filename}/intermed_480p.mp4" "output/${filename}/audio_128k.m4a" && \ 50 | 51 | # Create HLS playlists for each quality level 52 | echo -e "\nCreating HLS files (needed for Safari on iOS, Safari on Mac is already compatible with MPEG-DASH files)" && \ 53 | echo -e "Total # of frames: ${frames}\n\nCreating Full HD version (no upscaling, Step 1/4)" && \ 54 | ffmpeg -y -threads 0 -v error -stats -i "output/${filename}/intermed_1080p.mp4" -i "output/${filename}/audio_128k.m4a" -map 0:v:0 -map 1:a:0 -shortest -acodec copy -vcodec copy -hls_time 2 -hls_list_size 0 -hls_flags single_file "output/${filename}/segment_1.m3u8" && \ 55 | echo -e "Creating HD version (no upscaling, Step 2/4)" && \ 56 | ffmpeg -y -threads 0 -v error -stats -i "output/${filename}/intermed_720p.mp4" -i "output/${filename}/audio_128k.m4a" -map 0:v:0 -map 1:a:0 -shortest -acodec copy -vcodec copy -hls_time 2 -hls_list_size 0 -hls_flags single_file "output/${filename}/segment_2.m3u8" && \ 57 | echo -e "Creating DVD quality version (no upscaling, Step 3/4)" && \ 58 | ffmpeg -y -threads 0 -v error -stats -i "output/${filename}/intermed_480p.mp4" -i "output/${filename}/audio_128k.m4a" -map 0:v:0 -map 1:a:0 -shortest -acodec copy -vcodec copy -hls_time 2 -hls_list_size 0 -hls_flags single_file "output/${filename}/segment_3.m3u8" && \ 59 | echo -e "Creating audio only version (Step 4/4)" && \ 60 | ffmpeg -y -threads 0 -v error -stats -i "output/${filename}/audio_128k.m4a" -acodec copy -vcodec copy -hls_time 2 -hls_list_size 0 -hls_flags single_file "output/${filename}/segment_4.m3u8" && \ 61 | 62 | # Transform MPD-Master-Playlist to M3U8-Master-Playlist 63 | echo -e "\nCreating master M3U8-playlist for HLS" && \ 64 | xsltproc --stringparam run_id "segment" /app/mpd-to-m3u8/mpd_to_hls.xsl "output/${filename}/playlist.mpd" > "output/${filename}/playlist.m3u8" && \ 65 | 66 | # Cleanup 67 | echo -e "\nCleanup of intermediary files" && \ 68 | rm "output/${filename}/intermed_1080p.mp4" "output/${filename}/intermed_720p.mp4" "output/${filename}/intermed_480p.mp4" "output/${filename}/audio_128k.m4a"; 69 | 70 | # Add HTML code for easy inclusion in website 71 | if [[ -z "$2" ]]; then 72 | echo -e "\nAdd HTML files for playback to output folder"; 73 | cp /app/src/htaccess "output/${filename}/.htaccess"; 74 | ln -s .htaccess "output/${filename}/symbolic_link.htaccess"; 75 | cp /app/src/index.html "output/${filename}/index.html"; 76 | cp /app/src/plyr.html "output/${filename}/plyr.html"; 77 | cp /app/src/fluid-player.html "output/${filename}/fluid-player.html"; 78 | cp /app/src/videogular.html "output/${filename}/videogular.html"; 79 | cp /app/src/videojs.html "output/${filename}/videojs.html"; 80 | else 81 | if [ $2 != "--transcode-only" ]; then 82 | echo -e "\nAdd HTML files for playback to output folder"; 83 | cp /app/src/htaccess "output/${filename}/.htaccess"; 84 | ln -s .htaccess "output/${filename}/symbolic_link.htaccess"; 85 | cp /app/src/index.html "output/${filename}/index.html"; 86 | cp /app/src/plyr.html "output/${filename}/plyr.html"; 87 | cp /app/src/fluid-player.html "output/${filename}/fluid-player.html"; 88 | cp /app/src/videogular.html "output/${filename}/videogular.html"; 89 | cp /app/src/videojs.html "output/${filename}/videojs.html"; 90 | fi 91 | fi 92 | 93 | # Set permissions for newly created files and folders matching the video file's permissions 94 | echo -e "\nSetting permissions for all created files and folders & finishing"; 95 | chown -R `stat -c "%u:%g" "${input_file}"` output; 96 | -------------------------------------------------------------------------------- /webvtt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Text formatting details @https://misc.flogisoft.com/bash/tip_colors_and_formatting 4 | # Reset 5 | Color_Off='\e[0m' # Text Reset 6 | 7 | # Regular Colors 8 | Black='\e[0;30m' # Black 9 | Red='\e[0;31m' # Red 10 | Green='\e[0;32m' # Green 11 | Yellow='\e[0;33m' # Yellow 12 | Blue='\e[0;34m' # Blue 13 | Purple='\e[0;35m' # Purple 14 | Cyan='\e[0;36m' # Cyan 15 | White='\e[0;37m' # White 16 | 17 | # Bold 18 | Bold='\e[1m' # Bold on 19 | Bold_Off='\e[21m' # Bold off 20 | 21 | # Underline 22 | Underline='\e[4m' # Underline on 23 | Underline_Off='\e[24m' # Underline off 24 | 25 | # Blinking 26 | Blinking='\e[5m' # Blinking on 27 | Blinking_Off='\e[25m' # Blinking off 28 | 29 | # Reverse 30 | Reverse='\e[7m' # Reverse on 31 | Reverse_Off='\e[27m' # Reverse off 32 | 33 | # Background 34 | On_Black='\e[40m' # Black 35 | On_Red='\e[41m' # Red 36 | On_Green='\e[42m' # Green 37 | On_Yellow='\e[43m' # Yellow 38 | On_Light_Red='\e[101m' # Light Red 39 | On_Light_Blue='\e[104m' # Light Blue 40 | 41 | input_file="${1?Input file missing}" 42 | filename=$(basename "${input_file}") 43 | filename="${filename%.*}" 44 | frames=$(ffprobe -v error -select_streams v:0 -show_entries stream=nb_frames -of default=nokey=1:noprint_wrappers=1 "${input_file}") 45 | 46 | # Video Preview thumbnails (1/${thumbnail_timewindow} seconds) 47 | thumbnail_timewindow=7 48 | 49 | n=0 50 | h1=0 51 | m1=0 52 | s1=0 53 | h2=0 54 | m2=0 55 | s2=0 56 | thumbnail_counter=0 57 | thumbnail_width=120 58 | thumbnail_height=68 59 | 60 | shopt -s nullglob; 61 | 62 | # Create Video Preview thumbnails (1/${thumbnail_timewindow} seconds) 63 | mkdir -p "output/${filename}/thumbnails"; 64 | echo -e "\nCreating video preview thumbnails (1/${thumbnail_timewindow} seconds)"; 65 | rm -rf "output/${filename}/thumbnails/"*; 66 | ffmpeg -y -v error -i "${input_file}" -r 1/${thumbnail_timewindow} -vf scale=-1:120 -vcodec png "output/${filename}/thumbnails/thumbnail%02d.png" && \ 67 | rm -f "output/${filename}/thumbnails/thumbnail01.png"; 68 | 69 | cd "output/${filename}/thumbnails"; 70 | # Write thumbnail image names into file 71 | ls *.png > thumbnails.tmp; 72 | 73 | image_stack=""; 74 | delete_tmp_files=""; 75 | x=0; 76 | while read line 77 | do 78 | image_stack="${image_stack} -i ${line}"; 79 | delete_tmp_files="${delete_tmp_files} ${line}"; 80 | if [[ $thumbnail_counter -eq 0 ]]; then 81 | thumbnail_width=$(( exiv2 ${line} | grep " x " | grep -o '[0-9]*' | head -1 ) 2>/dev/null); 82 | thumbnail_height=$(( exiv2 ${line} | grep " x " | grep -o '[0-9]*' | tail -n +2 ) 2>/dev/null); 83 | echo -e "Thumbnail Dimensions: ${thumbnail_width} x ${thumbnail_height}"; 84 | fi 85 | thumbnail_counter=$((thumbnail_counter+1)); 86 | echo "thumbnails.png#xywh=${x},0,${thumbnail_width},${thumbnail_height}" >> thumbnails.vtt; 87 | x=$((x+thumbnail_width)); 88 | done < thumbnails.tmp 89 | if [[ $thumbnail_counter -gt 1 ]]; then 90 | ffmpeg -y -v error $image_stack -filter_complex hstack=inputs=$thumbnail_counter thumbnails.png; 91 | fi 92 | rm -f $delete_tmp_files; 93 | echo -e "Thumbnail count: ${thumbnail_counter}"; 94 | mv thumbnails.vtt thumbnails.tmp; 95 | 96 | # Insert matching WEBVTT timestamps for the preview images 97 | while read line 98 | do 99 | h1=$(( n / 3600 )); 100 | printf -v h1 "%02d" $h1; 101 | m1=$(( ( n / 60 ) % 60 )); 102 | printf -v m1 "%02d" $m1; 103 | s1=$(( n % 60 )); 104 | printf -v s1 "%02d" $s1; 105 | h2=$(( ( n + thumbnail_timewindow ) / 3600 )); 106 | printf -v h2 "%02d" $h2; 107 | m2=$(( ( ( n + thumbnail_timewindow ) / 60 ) % 60 )); 108 | printf -v m2 "%02d" $m2; 109 | s2=$(( ( n + thumbnail_timewindow ) % 60 )); 110 | printf -v s2 "%02d" $s2; 111 | echo $line|sed -e "/thumbnail/ { s/thumbnail/\n$h1:$m1:$s1.000 --> $h2:$m2:$s2.000\n&/ }" >> thumbnails.vtt; 112 | n=$((n+thumbnail_timewindow)); 113 | done < thumbnails.tmp 114 | 115 | rm -f thumbnails.tmp; 116 | # Insert new line "WEBVTT" at the start of thumbnails.vtt file 117 | sed -i '1 i\WEBVTT' thumbnails.vtt; 118 | # Append line-feed at the end of thumbnails.vtt file 119 | echo >> thumbnails.vtt; 120 | --------------------------------------------------------------------------------