mirror of https://github.com/adamdruppe/arsd.git
Merge pull request #450 from analogjupiter/pixmap-recorder
Pixmap Recorder revision Ⅰ
This commit is contained in:
commit
cb76629376
179
pixmaprecorder.d
179
pixmaprecorder.d
|
@ -9,20 +9,41 @@
|
|||
[FFmpeg](https://ffmpeg.org/about.html).
|
||||
|
||||
$(SIDEBAR
|
||||
Piping frame data to an independent copy of FFmpeg enables this library
|
||||
to be used with a wide range of verions of said third-party program
|
||||
and (hopefully) helps to reduce the chances of breaking changes.
|
||||
Piping frame data into an independent copy of FFmpeg
|
||||
enables this library to be used with a wide range of versions of said
|
||||
third-party program
|
||||
and (hopefully) helps to reduce the potential for breaking changes.
|
||||
|
||||
It also allows end-users to upgrade their possibilities by swapping the
|
||||
accompanying copy FFmpeg.
|
||||
|
||||
This could be useful in cases where software distributors can only
|
||||
provide limited functionality in their bundled binaries because of
|
||||
legal requirements like patent licenses.
|
||||
Keep in mind, support for more formats can be added to FFmpeg by
|
||||
linking it against external libraries; such can also come with
|
||||
additional distribution requirements that must be considered.
|
||||
These things might be perceived as extra burdens and can make their
|
||||
inclusion a matter of viability for distributors.
|
||||
)
|
||||
|
||||
### Tips and tricks
|
||||
|
||||
$(TIP
|
||||
The FFmpeg binary to be used can be specified by the optional
|
||||
constructor parameter `ffmpegExecutablePath`.
|
||||
|
||||
It defaults to `ffmpeg`; this will trigger the usual lookup procedures
|
||||
of the system the application runs on.
|
||||
On POSIX this usually means searching for FFmpeg in the directories
|
||||
specified by the environment variable PATH.
|
||||
On Windows it will also look for an executable file with that name in
|
||||
the current working directory.
|
||||
)
|
||||
|
||||
$(TIP
|
||||
The value of the `outputFormat` parameter of the constructor overloads
|
||||
is passed to FFmpeg via the `-f` option.
|
||||
The value of the `outputFormat` parameter of various constructor
|
||||
overloads is passed to FFmpeg via the `-f` (“format”) option.
|
||||
|
||||
Run `ffmpeg -formats` to get a list of available formats.
|
||||
)
|
||||
|
@ -32,6 +53,58 @@
|
|||
[PixmapRecorder.advancedFFmpegAdditionalOutputArgs|additional-output-args property].
|
||||
)
|
||||
|
||||
$(TIP
|
||||
Combining this module with [arsd.pixmappresenter|Pixmap Presenter]
|
||||
is really straightforward.
|
||||
|
||||
In the most simplistic case, set up a [PixmapRecorder] before running
|
||||
the presenter.
|
||||
Then call
|
||||
[PixmapRecorder.put|pixmapRecorder.record(presenter.framebuffer)]
|
||||
at the end of the drawing callback in the eventloop.
|
||||
|
||||
---
|
||||
auto recorder = new PixmapRecorder(60, /* … */);
|
||||
scope(exit) {
|
||||
const recorderStatus = recorder.stopRecording();
|
||||
}
|
||||
|
||||
return presenter.eventLoop(delegate() {
|
||||
// […]
|
||||
recorder.record(presenter.framebuffer);
|
||||
return LoopCtrl.redrawIn(16);
|
||||
}
|
||||
---
|
||||
)
|
||||
|
||||
$(TIP
|
||||
To use this module with [arsd.color] (which includes the image file
|
||||
loading functionality provided by other arsd modules),
|
||||
convert the
|
||||
[arsd.color.TrueColorImage|TrueColorImage] or
|
||||
[arsd.color.MemoryImage|MemoryImage] to a
|
||||
[arsd.pixmappaint.Pixmap|Pixmap] first by calling
|
||||
[arsd.pixmappaint.Pixmap.fromTrueColorImage|Pixmap.fromTrueColorImage()]
|
||||
or
|
||||
[arsd.pixmappaint.Pixmap.fromMemoryImage|Pixmap.fromMemoryImage()]
|
||||
respectively.
|
||||
)
|
||||
|
||||
### Examples
|
||||
|
||||
#### Getting started
|
||||
|
||||
1. Install FFmpeg (the CLI version).
|
||||
- Debian derivatives (with FFmpeg in their repos): `apt install ffmpeg`
|
||||
- Homebew: `brew install ffmpeg`
|
||||
- Chocolatey: `choco install ffmpeg`
|
||||
- Links to pre-built binaries can be found on <https://ffmpeg.org/download.html>.
|
||||
2. Determine where you’ve installed FFmpeg to.
|
||||
Ideally, it’s somewhere within “PATH” so it can be run from the
|
||||
command-line by just doing `ffmpeg`.
|
||||
Otherwise, you’ll need the specific path to the executable to pass it
|
||||
to the constructor of [PixmapRecorder].
|
||||
|
||||
---
|
||||
import arsd.pixmaprecorder;
|
||||
import arsd.pixmappaint;
|
||||
|
@ -72,6 +145,7 @@ import arsd.pixmappaint;
|
|||
import std.format;
|
||||
import std.path : buildPath;
|
||||
import std.process;
|
||||
import std.range : isOutputRange, OutputRange;
|
||||
import std.sumtype;
|
||||
import std.stdio : File;
|
||||
|
||||
|
@ -83,26 +157,24 @@ private @safe {
|
|||
return stderr;
|
||||
}
|
||||
|
||||
auto stdoutFauxSafe() @trusted {
|
||||
import std.stdio : stderr;
|
||||
|
||||
return stderr;
|
||||
}
|
||||
|
||||
auto stderr() {
|
||||
return stderrFauxSafe;
|
||||
}
|
||||
|
||||
auto stdout() {
|
||||
return stderrFauxSafe;
|
||||
}
|
||||
|
||||
alias RecorderOutput = SumType!(string, File);
|
||||
}
|
||||
|
||||
final class PixmapRecorder {
|
||||
/++
|
||||
Video file encoder
|
||||
|
||||
@safe:
|
||||
Feed in video data frame by frame to encode video files
|
||||
in one of the various formats supported by FFmpeg.
|
||||
|
||||
This is a convenience wrapper for piping pixmaps into FFmpeg.
|
||||
FFmpeg will render an actual video file from the frame data.
|
||||
This uses the CLI version of FFmpeg, no linking is required.
|
||||
+/
|
||||
final class PixmapRecorder : OutputRange!(const(Pixmap)) {
|
||||
|
||||
private {
|
||||
string _ffmpegExecutablePath;
|
||||
|
@ -118,6 +190,8 @@ final class PixmapRecorder {
|
|||
bool _outputIsOurs = false;
|
||||
}
|
||||
|
||||
@safe:
|
||||
|
||||
private this(
|
||||
string ffmpegExecutablePath,
|
||||
double frameRate,
|
||||
|
@ -133,32 +207,34 @@ final class PixmapRecorder {
|
|||
}
|
||||
|
||||
/++
|
||||
Prepares a recorder for encoding video frames
|
||||
into the specified file pipe.
|
||||
Prepares a recorder for encoding a video file into the provided pipe.
|
||||
|
||||
$(WARNING
|
||||
Certain formats cannot be produced in pipes by FFmpeg.
|
||||
Look out for error message like such:
|
||||
FFmpeg cannot produce certain formats in pipes.
|
||||
Look out for error messages such as:
|
||||
|
||||
($BLOCKQUOTE
|
||||
`[mp4 @ 0xdead1337beef] muxer does not support non seekable output`
|
||||
$(BLOCKQUOTE
|
||||
`[mp4 @ 0xdead1337beef] muxer does not support non-seekable output`
|
||||
)
|
||||
|
||||
This is not a limitation of this library (but rather one of FFmpeg).
|
||||
Let FFmpeg output the video to file path instead;
|
||||
check out the other overloads of this constructor.
|
||||
|
||||
Nevertheless, it’s still possible to use the affected formats.
|
||||
Let FFmpeg output the video to the file path instead;
|
||||
check out the other constructor overloads.
|
||||
)
|
||||
|
||||
Params:
|
||||
frameRate = Framerate of the video output; in frames per second.
|
||||
output = File handle to write the video output to.
|
||||
outputFormat = Video (container) format to output.
|
||||
This is value passed to FFmpeg via the `-f` option.
|
||||
This value is passed to FFmpeg via the `-f` option.
|
||||
log = Target file for the stderr log output of FFmpeg.
|
||||
This is where error messages are written to.
|
||||
ffmpegExecutablePath = Path to the FFmpeg executable
|
||||
(e.g. `ffmpeg`, `ffmpeg.exe` or `/usr/bin/ffmpeg`).
|
||||
/* Keep this table in sync with the ones of other overloads. */
|
||||
|
||||
$(COMMENT Keep this table in sync with the ones of other overloads.)
|
||||
+/
|
||||
public this(
|
||||
double frameRate,
|
||||
|
@ -182,8 +258,14 @@ final class PixmapRecorder {
|
|||
}
|
||||
|
||||
/++
|
||||
Prepares a recorder for encoding video frames
|
||||
into a video file saved to the specified path.
|
||||
Prepares a recorder for encoding a video file
|
||||
saved to the specified path.
|
||||
|
||||
$(TIP
|
||||
This allows FFmpeg to seek through the output file
|
||||
and enables the creation of file formats otherwise not supported
|
||||
when using piped output.
|
||||
)
|
||||
|
||||
Params:
|
||||
frameRate = Framerate of the video output; in frames per second.
|
||||
|
@ -192,15 +274,16 @@ final class PixmapRecorder {
|
|||
FFmpeg will use this to autodetect the format
|
||||
when no `outputFormat` is provided.
|
||||
log = Target file for the stderr log output of FFmpeg.
|
||||
This is where error messages are written to.
|
||||
This is where error messages are written to, as well.
|
||||
outputFormat = Video (container) format to output.
|
||||
This is value passed to FFmpeg via the `-f` option.
|
||||
This value is passed to FFmpeg via the `-f` option.
|
||||
If `null`, the format is not provided and FFmpeg
|
||||
will try to autodetect the format from the filename
|
||||
of the `outputPath`.
|
||||
ffmpegExecutablePath = Path to the FFmpeg executable
|
||||
(e.g. `ffmpeg`, `ffmpeg.exe` or `/usr/bin/ffmpeg`).
|
||||
/* Keep this table in sync with the ones of other overloads. */
|
||||
|
||||
$(COMMENT Keep this table in sync with the ones of other overloads.)
|
||||
+/
|
||||
public this(
|
||||
double frameRate,
|
||||
|
@ -215,8 +298,8 @@ final class PixmapRecorder {
|
|||
in ((outputFormat is null) || outputFormat != "")
|
||||
in (ffmpegExecutablePath != "") {
|
||||
|
||||
// Sanitize output path
|
||||
// if it would get confused with a command-line arg.
|
||||
// Sanitize the output path
|
||||
// if it were to get confused with a command-line arg.
|
||||
// Otherwise a relative path like `-my.mkv` would make FFmpeg complain
|
||||
// about an “Unrecognized option 'out.mkv'”.
|
||||
if (outputPath[0] == '-') {
|
||||
|
@ -234,16 +317,16 @@ final class PixmapRecorder {
|
|||
|
||||
/++
|
||||
$(I Advanced users only:)
|
||||
Additional command-line arguments passed to FFmpeg.
|
||||
Additional command-line arguments to be passed to FFmpeg.
|
||||
|
||||
$(WARNING
|
||||
The values provided through this property function are not
|
||||
validated and passed verbatim to FFmpeg.
|
||||
)
|
||||
|
||||
$(PITFAL
|
||||
$(PITFALL
|
||||
If code makes use of this and FFmpeg errors,
|
||||
check the arguments provided here this first.
|
||||
check the arguments provided here first.
|
||||
)
|
||||
+/
|
||||
void advancedFFmpegAdditionalOutputArgs(string[] args) {
|
||||
|
@ -258,6 +341,9 @@ final class PixmapRecorder {
|
|||
return _input.writeEnd.isOpen;
|
||||
}
|
||||
|
||||
/// ditto
|
||||
alias isRecording = isOpen;
|
||||
|
||||
private string[] buildFFmpegCommand() pure {
|
||||
// Build resolution as understood by FFmpeg.
|
||||
const string resolutionString = format!"%sx%s"(
|
||||
|
@ -310,7 +396,7 @@ final class PixmapRecorder {
|
|||
|
||||
$(SIDEBAR
|
||||
Variable/dynamic resolution is neither supported by this library
|
||||
nor most real-world applications.
|
||||
nor by most real-world applications.
|
||||
)
|
||||
|
||||
$(NOTE
|
||||
|
@ -318,7 +404,7 @@ final class PixmapRecorder {
|
|||
There’s usually no need to call this manually.
|
||||
)
|
||||
+/
|
||||
void open(Size resolution)
|
||||
void open(const Size resolution)
|
||||
in (!this.isOpen) {
|
||||
// Save resolution for sanity checks.
|
||||
_resolution = resolution;
|
||||
|
@ -368,13 +454,13 @@ final class PixmapRecorder {
|
|||
alias startRecording = close;
|
||||
|
||||
/++
|
||||
Provides the next video frame to encode.
|
||||
Supplies the next frame to the video encoder.
|
||||
|
||||
$(TIP
|
||||
This function automatically calls [open|open()] if necessary.
|
||||
)
|
||||
+/
|
||||
void put(Pixmap frame) {
|
||||
void put(const Pixmap frame) {
|
||||
if (!this.isOpen) {
|
||||
this.open(frame.size);
|
||||
} else {
|
||||
|
@ -384,6 +470,9 @@ final class PixmapRecorder {
|
|||
_input.writeEnd.rawWrite(frame.data);
|
||||
}
|
||||
|
||||
/// ditto
|
||||
alias record = put;
|
||||
|
||||
/++
|
||||
Ends the recording process.
|
||||
|
||||
|
@ -396,7 +485,7 @@ final class PixmapRecorder {
|
|||
+/
|
||||
int close() {
|
||||
if (!this.isOpen) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_input.writeEnd.flush();
|
||||
|
@ -411,3 +500,9 @@ final class PixmapRecorder {
|
|||
/// ditto
|
||||
alias stopRecording = close;
|
||||
}
|
||||
|
||||
// self-test
|
||||
private {
|
||||
static assert(isOutputRange!(PixmapRecorder, Pixmap));
|
||||
static assert(isOutputRange!(PixmapRecorder, const(Pixmap)));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue