This repository can be used to burn in images into a video with the help of FFmpeg. A Docker file is provided that builds a Docker image containing FFmpeg and BaseX which provides a RestXQ service. Also a simple web interface is available that allows to add a job and to track its status.
Just execute the following command to build the Docker image:
docker build -t video_image_burner .
To run the image, the web interface port (8984) has to be mapped to a local port (here: 9010).
Furthermore shared folders are used to exchange the input/output files. These folders have to be bound to local folders:
/root/input
for input (here:/vib-files
orc:\vib-files
)/root/output
for output (here:/vib-out
orc:\vib-out
)
This can be achieved with the following command (Linux host):
docker run \
--mount type=bind,source=/vib-files,target=/root/input \
--mount type=bind,source=/vib-out,target=/root/output \
-p 9010:8984 video_image_burner
The value for the source
parameter needs to be set to a local folder
on the system where Docker is executed. On a Windows host this may look
like:
docker run ^
--mount type=bind,source=c:\vib-files,target=/root/input ^
--mount type=bind,source=c:\vib-out,target=/root/output ^
-p 9010:8984 video_image_burner
If the Docker image is executed on a Linux host which provides hardware
acceleration using VAAPI, it can be employed for encoding. For this the
following additional parameter has to be added to the docker run
call:
--device /dev/dri
If hardware acceleration is available, a checkbox to use it is provided in the web interface.
The burner can also be used without Docker. This requires BaseX to be
installed (tested with version 9.3), including Saxon (HE) 9 (tested with
version 9.9.1.6). Hereby Saxon's saxon9he.jar
has to be copied to the
lib/custom
subfolder of the BaseX installation. Furthermore the FFmpeg
commands ffmpeg
and ffprobe
must be available.
The following BaseX command has then to be executed in the repository root folder to start the burner (which will be available on port 8984):
basexhttp
The web interface is available at:
localhost:9010
As mentioned above shared folders are used to exchange input/output files between a user and the Video Image Burner (due to the file sizes). Therefore the shared folders have somehow to be made accessible for users e.g. via FTP or as a Windows share.
First a source file (currently: MP4 with H.264 video) has to be put into the input folder. It then can be selected in the pull-down menu as source.
Furthermore the corresponding images have to be provided. Currently
this happens in form of a ZIP archive. Every file in this archive
represents a screen update (e.g. new content shown or any visible
content is hidden). Each file has to be a PNG file with a transparent
background and its resolution must be the same as the resolution of the
source video. A filename also represents the point in time (in seconds)
from which on a file shall be shown (until the next file "gets active")
e.g. 12.png
or 420.84.png
.
Finally a few options can be configured e.g. whether the video shall be scaled to a certain resolution or not. If the Docker host provides hardware acceleration, it can also be enabled/disabled for the process.
When the process is started, a status page is shown. It displays the progress of the current burn-in process and further details including the FFmpeg output. It also indicates the successful completion of the process - or any errors that occur.
After the process has finished, the output folder contains the resulting
output file. If not yet present, the file extension .mp4
is appended.
Adding a job and querying the current status can also be done using the REST interface. Similar to the web interface, input/output files have to be exchanged using a shared folder, too.
A list of input files available in the input folder can be retrieved
using the essences
GET request.
The burn-in process is started using the jobs
POST request. The
current job status is retrieved using the jobs/<job-id>
GET request.
Note that Cross-Origin Resource Sharing (CORS) is enabled i.e. requests
from any origin are processed. This behaviour can be disabled by
removing the paragraph related to CORS in /webapp/WEB-INF/web.xml
.
If a request succeeds, it returns the HTTP status code 200 OK
.
Otherwise code 400 Bad Request
is returned, together with a JSON
structure consisting of a string error
that further describes the
reason for failing, for example:
{
"error":"The requested job does not exist."
}
This request is a helper request that provides an (ordered) list of all
available input files (suitable/designated for embedding) in the input
folder. Currently this includes all files that have an .mp4
extension.
No parameters are available for this request.
Upon success the response is in JSON format and simply an array of strings, for example:
[
"foo.mp4",
"bar.mp4"
]
Each string is the filename of an essence available in the defined input folder.
This request is used to add a new burn-in job. The new job is immediately started despite any other already running jobs. Thus it has to be taken care that only a single job is active in parallel (or at least not too many jobs).
The following request parameters are available:
essence
(mandatory): The filename of the input file which is stored in the input folder and into which the images shall be burnt in.images
(mandatory): A ZIP file which contains all the images to be burnt in. For this reason each image filename has to correspond to the media time at which it actually "gets active".resolution
(optional): The desired target resolution of the output video. An empty string (default) to indicate that the video shall not be resized, otherwise a value of the format1280x720
(= width by height in pixels).quality
(mandatory): The target quality of the output video. In general the Constant Rate Factor (CRF) is used (range 0 to 63), which is an abstract value that reflects the desired quality/size tradeoff to get a constant output quality (the lower the value, the better the quality). In case of hardware acceleration, a similar means called "global quality" (range 1 to 51) is used internally. Note that in tests a video processed with the value 0 (only allowed without hardware acceleration) was not played by all tested players. Therefore it is recommended to use a different value.hwaccel
(optional): Set to1
if the encoding shall be done using hardware acceleration (requires hardware acceleration to be available), otherwise set to0
(default).
Note that the HTTP content type multipart/form-data
has to be used for
this request, as a file is uploaded here.
Upon success the response is in JSON format and contains the ID of the newly created job as part of the following JSON object:
{
"job_id": <job_id>
}
This request allows to query the current status of a previously added job. The information is still available after a job has ended.
The following request parameters are available:
job_id
(mandatory): The job ID of the queried job.verbose
(optional): Set to1
if additional fields (log
andcmdline
) shall be contained in the response, otherwise set to0
(default).
Upon success the response is in JSON format and contains different fields as part of the following JSON object:
{
"job_id": <job_id>
"essence": <essence>
"exitStatus": <exitStatus>
"progressPercentage": <progressPercentage>
"progressText": <progressText>
"log": <log>
"cmdline": <cmdline>
}
The following field values are transmitted:
job_id
: The job ID of the queried job.essence
: The filename of the input file which is stored in the input folder and into which the images shall be burnt in.exitStatus
: The exit code of the FFmpeg process or-1
, if FFmpeg is still running. If the process finished without any error, the exit code will be0
. Otherwise it will be a value larger than zero and further details can be retrieved from FFmpeg's output messages.progressPercentage
: Percentage of the processed video timeline.progressText
: Status message describing the conversion progress, intended for user display.log
: The FFmpeg output messages (fromstderr
) so far.cmdline
: The command line that was used to invoke FFmpeg.
Note: The fields log
and cmdline
are only available in verbose mode
(see above).
The following example requests are done using cURL. A burner running
locally is assumed. Note that Windows uses a different character for
line continuation (^
instead of \
).
Retrieve a list of all available essences:
curl http://localhost:9010/essences
Burn images (stored in image.zip
) into the essence example.mp4
,
using a target quality of 23
and hardware acceleration:
curl -F essence=example.mp4 -F [email protected] \
-F quality=23 -F hwaccel=1 http://localhost:9010/jobs
Retrieve the current status of a certain job (with verbose output):
curl -d verbose=1 http://localhost:9010/jobs/21646c0b-dba7-4d2a-9cdb-24c64f8291ca
The web and REST interfaces are provided using BaseX together with RestXQ. The Saxon (HE) processor is used as well, as it is able to process XSLT versions newer than 1.0. The actual burn-in process is done using FFmpeg. In contrast to the other prerequisites, it is not downloaded but compiled when the Docker image is built, in order to include the necessary codecs/filters e.g. for hardware acceleration.
The uploaded images are combined to a virtual video file using FFmpeg's
concat
container (a custom format by FFmpeg). This file is then
rendered on top of the actual source file video picture. If selected,
the video image is finally scaled to the specified resolution (if
desired) prior to encoding it to H.264 again (using a certain
quality/CRF).
The following files are relevant for the actual application:
.basexhome
: empty BaseX helper file to indicate home directory.create_concat_file_from_dir.xsl
: XSLT used to create a helper file inconcat
format, refering to all images in a certain directory.webapp/video_image_burner.xqm
: application source code as XQuery module.webapp/WEB-INF/jetty.xml
: Jetty web server configwebapp/WEB-INF/web.xml
: web application config
The process itself starts by creating a temporary directory in order to
store different temporary files during conversion. This also includes
the images unpacked from the ZIP file. In addition the concat
container (as described above) is automatically created using an XSLT
transformation.
A helper script is assembled in order to call FFmpeg with the required
parameters. This script also allows to redirect the stderr
output to a
log file which is queried later for status updates. An additional file
contains progress data that is regularly updated by FFmpeg. A further
file contains (static) internal job information required later.
Before the actual conversion is started, FFprobe is executed to determine the input file duration. This information is used later during the conversion itself for proper progress information.
When the current status is retrieved using the respective REST API call, constant information (e.g. the essence name or the command line to call FFmpeg) is combined with information that is read from regularly updated files e.g. the log messages so far and the current progress. For this reason the progress is not shown in the log to prevent to "pollute" it, hiding other, more important messages and to not require too much scrolling.
The actual combination of the images with the original video is done
using FFmpeg filters. As filters are involved which have more than one
input or output, FFmpeg's complex filtergraph has to be used. In general
the overlay
filter is used to render the images onto the video image.
If desired, the output is then scaled to a different resolution. In case
of hardware acceleration, the image is uploaded to the graphic card
before. In such a case the scaling is done in hardware, too.
So for each job, the following temporary files are created and used internally (e.g. further static information required later) and stored in the system's temp folder:
vib-log-<job-id>.log
: FFmpeg's output tostderr
; expands while FFmpeg is running.vib-log-<job-id>.log.duration
: FFprobe's output containing the essence duration.vib-log-<job-id>.log.progress
: FFmpeg progress/status; regularly updated by FFmpeg while running.vib-log-<job-id>.log.finished
: FFmpeg exit code; created after FFmpeg exits.vib-log-<job-id>.log.info.xml
: burner internal job data e.g. the used FFmpeg command line.
In the system's temp folder for each job also an additional folder is created which contains the following files:
concat.txt
: Theconcat
container, referring to the images.vib_burn
: The shell script that does the actual work e.g. to invoke FFmpeg.*.png
: The image files, extracted from the uploaded ZIP file.
This job folder is deleted after the job finishes.