Skip to content

Commit

Permalink
refactor: Reorganise ffmpeg spec installation requirements
Browse files Browse the repository at this point in the history
  • Loading branch information
RyanSkraba committed Dec 6, 2024
1 parent 28ccc7f commit b4046d6
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ case class Card(
/** The title card in PNG format. */
val png: FileMaker = maker
.green(dstPath = dirPng / (filename + ".png"), tag = "png") {
Ffmpeg().osProc(Card.Inkscape, "-w", "1920", "-h", "1080", svg.dst.get, "--export-filename", _)
Ffmpeg().osProc(Ffmpeg.InkscapeCmd, "-w", "1920", "-h", "1080", svg.dst.get, "--export-filename", _)
}

private lazy val mp4Cache = collection.mutable.Map[Int, FileMaker]()
Expand All @@ -53,9 +53,6 @@ case class Card(

object Card {

/** The command to call to run the Inkscape CLI. */
val Inkscape: String = "inkscape"

/** Given a template string, searches and replaces some lines in the template.
*
* The tags in the template look like `[[TAG4.3]]` where the first digit is the total number of lines that were found
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ case class Ffmpeg(
lazy val streamInfo: ujson.Obj = ujson
.read(
osProc(
"ffprobe",
Ffprobe,
Seq("-v", "quiet"),
Seq("-print_format", "json"),
Seq("-show_format", "-show_streams"),
Expand Down Expand Up @@ -110,7 +110,7 @@ case class Ffmpeg(
def frameChunkInfo(start: String, end: String, keyFramesOnly: Boolean = false): ujson.Arr = ujson
.read(
osProc(
"ffprobe",
Ffprobe,
Seq("-v", "quiet"),
if (keyFramesOnly) Seq("-skip_frame", "nokey") else Seq(),
Seq("-print_format", "json"),
Expand Down Expand Up @@ -204,7 +204,7 @@ case class Ffmpeg(
val frameRateArg: Seq[String] = if (frameRate != 25) Seq("-framerate", frameRate.toString) else Seq.empty[String]

val cmd = osProc(
"ffmpeg",
FfmpegCmd,
frameRateArg,
// Loop the input image indefinitely for a certain number of seconds
Seq("-loop", "1", "-t", duration, "-i", srcPng).map(_.toString),
Expand Down Expand Up @@ -256,7 +256,7 @@ case class Ffmpeg(
val frameRateArg: Seq[String] = if (frameRate == 25) Seq("-framerate", frameRate.toString) else Seq.empty[String]

val cmd = osProc(
"ffmpeg",
FfmpegCmd,
frameRateArg,
if (loop > 1) Seq("-stream_loop", (loop - 1).toString) else Seq.empty[String],
// Use a pattern glob to select the incoming frames
Expand Down Expand Up @@ -296,7 +296,7 @@ case class Ffmpeg(
fontFile: Option[os.Path] = None
): Ffmpeg = {
val cmd = osProc(
"ffmpeg",
FfmpegCmd,
Seq("-i", mp4).map(_.toString),
// Use a video filter to draw text over the frames
"-vf",
Expand Down Expand Up @@ -351,7 +351,7 @@ case class Ffmpeg(
aevalsrc: String = ""
): Ffmpeg = {
val cmd = osProc(
"ffmpeg",
FfmpegCmd,
Seq("-i", mp4).map(_.toString),
// The generated sound
Seq("-f", "lavfi", "-i", s"aevalsrc=${if (aevalsrc.nonEmpty) aevalsrc else aevalsrcSweep()}:s=$audioRate:d=$dt"),
Expand Down Expand Up @@ -387,7 +387,7 @@ case class Ffmpeg(
srcMp4s.map(_.toString.replaceAll(" ", "\\\\ ")).map(mp4 => s"file file:$mp4\n").mkString
)
val cmd = osProc(
"ffmpeg",
FfmpegCmd,
"-f",
"concat",
"-safe",
Expand Down Expand Up @@ -420,7 +420,7 @@ case class Ffmpeg(
reencode: Boolean
): Ffmpeg = {
val cmd = osProc(
"ffmpeg",
FfmpegCmd,
// The start time, either in seconds or HH:MM:SS
start.toSeq.filterNot(_.isEmpty).flatMap(Seq("-ss", _)),
// The end time, either in seconds or HH:MM:SS
Expand Down Expand Up @@ -453,7 +453,7 @@ case class Ffmpeg(
if (end == 0) None else Some(s"afade=t=out:st=${duration - end}:d=$end")
).flatten.mkString(",")
val cmd = osProc(
"ffmpeg",
FfmpegCmd,
Seq("-i", mp4).map(_.toString),
Seq("-af", fadeFilter),
Seq("-c:v", "copy"),
Expand All @@ -470,7 +470,7 @@ case class Ffmpeg(
* An instance containing the destination MP4 and the command results.
*/
def normalizeMp4(dstMp4: os.Path): os.CommandResult = osProc(
"ffmpeg",
FfmpegCmd,
Seq("-i", mp4).map(_.toString),
Seq("-filter:a", "loudnorm=I=-15 [f] ; [f] afftdn=nr=97 [g]; [g] highpass=f=100"),
// Audio codec
Expand All @@ -482,6 +482,22 @@ case class Ffmpeg(

object Ffmpeg {

/** The command to call to run the Inkscape CLI. */
val InkscapeCmd: String = "inkscape"

/** The command to call to run the Ffmpeg CLI. */
val FfmpegCmd: String = "ffmpeg"

/** The command to call to run the Ffmpeg CLI. */
val Ffprobe: String = "ffprobe"

/** Determine if all of the commands have been installed. */
val enabled: Boolean = {
import scala.sys.process._
try { s"$FfmpegCmd -version".! == 0 && s"$Ffprobe -version".! == 0 && s"$InkscapeCmd --version".! == 0 }
catch { case _: Exception => false }
}

/** @param loFrequency
* The low frequency in hertz
* @param hiFrequency
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class CardSpec extends AnyFunSpecLike with BeforeAndAfterAll with Matchers {

describe("Create a basic svg file using the card") {
it("should create the files as needed") {
assume(Ffmpeg.enabled, "ffmeg and inkscape are required for this test")
val flag = Card(
genSvg = _ => CardSpec.SvgFlag,
filename = "canada_flag",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.skraba.byexample.scala.ammonite.video

import com.skraba.byexample.scala.ammonite.OsPathScalaRelectIOConverters._
import com.skraba.byexample.scala.ammonite.video.Card.Inkscape
import com.skraba.byexample.scala.ammonite.video.Ffmpeg._
import org.scalactic.source.Position
import org.scalatest.{BeforeAndAfterAll, Tag}
import org.scalatest.funspec.AnyFunSpecLike
import org.scalatest.matchers.should.Matchers

import java.nio.file.StandardCopyOption
import scala.collection.mutable
import scala.reflect.io.{Directory, File}
import scala.util.Properties

Expand Down Expand Up @@ -76,13 +75,6 @@ class FfmpegSpec extends AnyFunSpecLike with BeforeAndAfterAll with Matchers {
* run the tests. If `ffmpeg` or `ffprobe` are not present, the word is replaced entirely by ignored calls.
*/
protected class FfmpegItWord extends ItWord {

/** Determine if the tests should be run or not. */
val enabled: Boolean = {
import scala.sys.process._
try { "ffmpeg -version".! == 0 && "ffprobe -version".! == 0 && s"$Inkscape --version".! == 0 }
catch { case _: Exception => false }
}
override def apply(specText: String, testTags: Tag*)(testFun: => Any)(implicit pos: Position): Unit = {
// Since we override `it`, we fall back on the equivalent `they` if the tests are enabled.
if (enabled) they(specText)(testFun)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.skraba.byexample.scala

import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach}
import org.scalatest.funspec.AnyFunSpecLike
import org.scalatest.matchers.should.Matchers

Expand Down

0 comments on commit b4046d6

Please sign in to comment.