Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

動画合成 #41

Merged
merged 18 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions src/main/scala/com/github/windymelt/zmm/ChromiumCli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,25 @@ package com.github.windymelt.zmm

import cats.effect.IO
import cats.effect.kernel.Resource
import cats.effect.std.Mutex
import cats.implicits._

class ChromiumCli extends Cli with infrastructure.ChromeScreenShotComponent {
val chromiumCommand =
sys.env.get("CHROMIUM_CMD").getOrElse(config.getString("chromium.command"))

def screenShotResource: IO[Resource[IO, ScreenShot]] = {
IO.println(
s"""[configuration] chromium command: ${chromiumCommand}"""
) >> Resource
.eval(
new ChromeScreenShot(
chromiumCommand,
ChromeScreenShot.Quiet,
chromiumNoSandBox
).pure[IO]
for {
_ <- IO.println(
s"""[configuration] chromium command: ${chromiumCommand}"""
)
.pure[IO]
mu <- Mutex[IO]
} yield mu.lock.map { _ =>
new ChromeScreenShot(
chromiumCommand,
ChromeScreenShot.Quiet,
chromiumNoSandBox
)
}
}

}
46 changes: 38 additions & 8 deletions src/main/scala/com/github/windymelt/zmm/Cli.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package com.github.windymelt.zmm

import cats.effect.ExitCode
import cats.effect.IO
import cats.effect.IOApp
import cats.effect.ExitCode
import java.io.OutputStream
import org.http4s.syntax.header
import com.github.windymelt.zmm.domain.model.Context
import scala.concurrent.duration.FiniteDuration
import cats.effect.std.Mutex
import com.github.windymelt.zmm.domain.model.Context
import com.github.windymelt.zmm.domain.model.VoiceBackendConfig
import org.http4s.syntax.header

import java.io.OutputStream
import scala.concurrent.duration.FiniteDuration

trait Cli
extends domain.repository.FFmpegComponent
Expand All @@ -29,7 +30,10 @@ trait Cli
sys.env.get("VOICEVOX_URI") getOrElse config.getString("voicevox.apiUri")
def voiceVox: VoiceVox = new ConcreteVoiceVox(voiceVoxUri)
def ffmpeg =
new ConcreteFFmpeg(config.getString("ffmpeg.command"), ConcreteFFmpeg.Quiet)
new ConcreteFFmpeg(
config.getString("ffmpeg.command"),
ConcreteFFmpeg.Quiet
) // TODO: respect construct parameter
val chromiumNoSandBox = sys.env
.get("CHROMIUM_NOSANDBOX")
.map(_ == "1")
Expand Down Expand Up @@ -161,6 +165,32 @@ trait Cli
zippedVideo <- backgroundIndicator("Zipping silent video and audio").use {
_ => ffmpeg.zipVideoWithAudio(video, audio)
}
composedVideo <- backgroundIndicator("Composing Video").surround {
// もし設定されていればビデオを合成する。BGMと同様、同じビデオであれば結合する。
val videoWithDuration: Seq[(Option[os.Path], FiniteDuration)] =
sayCtxPairs
.map(p => p._2.video.map(os.pwd / os.RelPath(_)))
.zip(pathAndDurations.map(_._2))

val reductedVideoWithDuration = groupReduction(videoWithDuration)

// 環境によっては上書きに失敗する?ので出力ファイルが存在する場合削除する
val outputFile = os.pwd / "output_composed.mp4"
os.remove(outputFile, checkExists = false)

reductedVideoWithDuration.filter(_._1.isDefined).size match {
case 0 =>
IO.delay {
os.move(zippedVideo, outputFile)
outputFile
}
case _ =>
ffmpeg.composeVideoWithDuration(
zippedVideo,
reductedVideoWithDuration
)
}
}
_ <- backgroundIndicator("Applying BGM").use { _ =>
// BGMを合成する。BGMはコンテキストで割り当てる。sayCtxPairsでsayごとにコンテキストが確定するので、同じBGMであれば結合しつつ最終的なDurationを計算する。
// たとえば、BGMa 5sec BGMa 5sec BGMb 10sec であるときは、 BGMa 10sec BGMb 10secに簡約される。
Expand All @@ -178,11 +208,11 @@ trait Cli
reductedBgmWithDuration.filter(_._1.isDefined).size match {
case 0 =>
IO.pure(
os.move(zippedVideo, outputFile)
os.move(composedVideo, outputFile)
) // Dirty fix. TODO: fix here
case _ =>
ffmpeg.zipVideoWithAudioWithDuration(
zippedVideo,
composedVideo,
reductedBgmWithDuration
)
}
Expand Down
8 changes: 5 additions & 3 deletions src/main/scala/com/github/windymelt/zmm/Main.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.github.windymelt.zmm

import cats.effect.ExitCode
import cats.effect.IO
import cats.effect.IOApp
import cats.effect.ExitCode
import java.io.OutputStream
import org.http4s.syntax.header
import com.monovore.decline.Opts
import com.monovore.decline.effect.CommandIOApp
import org.http4s.syntax.header

import java.io.OutputStream

object Main
extends CommandIOApp(
Expand All @@ -30,6 +31,7 @@ object Main
}
case TargetFile(file, screenShotBackend) =>
val cli = screenShotBackend match {
// TODO: ffmpeg verbosityをcli opsから設定可能にする
case Some(ScreenShotBackend.Chrome) => new ChromiumCli()
case Some(ScreenShotBackend.Firefox) => new FirefoxCli()
case _ => defaultCli
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ final case class Context(
Map.empty, // id -> (code, lang?)
maths: Map[String, String] = Map.empty, // id -> LaTeX string
sic: Option[String] = None, // 代替読みを設定できる(数式などで使う)
silentLength: Option[FiniteDuration] = None // by=silentな場合に停止する時間
silentLength: Option[FiniteDuration] = None, // by=silentな場合に停止する時間
video: Option[String] = None // 背景に合成する動画
// TODO: BGM, fontColor, etc.
) {
def atv = additionalTemplateVariables // alias for template
Expand Down Expand Up @@ -87,7 +88,8 @@ object Context {
x.codes |+| y.codes, // Map の Monoid性を応用すると、同一idで書かれたコードは結合されるという好ましい特性が表われるのでこうしている。additionalTemplateVariablesに畳んでもいいかもしれない。現在のコードはadditionalTemplateVariablesに入れている
maths = x.maths |+| y.maths,
sic = y.sic orElse x.sic,
silentLength = y.silentLength <+> x.silentLength
silentLength = y.silentLength <+> x.silentLength,
video = y.video <+> x.video
)
}
def empty: Context = Context.empty
Expand Down Expand Up @@ -130,7 +132,8 @@ object Context {
sic = firstAttrTextOf(e, "sic"),
silentLength = firstAttrTextOf(e, "silent-length").map(l =>
FiniteDuration.apply(Integer.parseInt(l), "second")
)
),
video = firstAttrTextOf(e, "video")
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ trait FFmpegComponent {
videoPath: os.Path,
audioDurationPair: Seq[(Option[os.Path], FiniteDuration)]
): IO[os.Path]
def composeVideoWithDuration(
baseVideoPath: os.Path,
overlayVideoDurationPair: Seq[(Option[os.Path], FiniteDuration)]
): IO[os.Path]
def zipVideoWithAudio(videoPath: os.Path, audioPath: os.Path): IO[os.Path]
def generateSilentWav(path: os.Path, length: FiniteDuration): IO[os.Path]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,9 @@ trait ScreenShotComponent {
windowWidth: Int = 1920,
windowHeight: Int = 1080
): IO[os.Path]

/** ユーザの入力によってスクリーンショット実装が切り替わるので、それを内部で判別できるようにするための識別子。
*/
val screenShotImplementation: String
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.github.windymelt.zmm
package infrastructure

import cats.effect.IO
import cats.implicits._
import cats.effect.kernel.Resource
import cats.implicits._

trait ChromeScreenShotComponent {
self: domain.repository.ScreenShotComponent =>
Expand All @@ -21,6 +21,7 @@ trait ChromeScreenShotComponent {
verbosity: ChromeScreenShot.Verbosity,
noSandBox: Boolean = false
) extends ScreenShot {
val screenShotImplementation = "chrome"
val stdout = verbosity match {
case ChromeScreenShot.Quiet => os.Pipe
case ChromeScreenShot.Verbose => os.Inherit
Expand All @@ -34,18 +35,22 @@ trait ChromeScreenShotComponent {
case true =>
os.proc(
chromeCommand,
"--headless",
"--headless=new",
"--no-sandbox",
"--hide-scrollbars",
s"--screenshot=${htmlFilePath}.png",
s"--window-size=${windowWidth},${windowHeight}",
"--default-background-color=00000000",
htmlFilePath
)
case false =>
os.proc(
chromeCommand,
"--headless",
"--headless=new",
"--hide-scrollbars",
s"--screenshot=${htmlFilePath}.png",
s"--window-size=${windowWidth},${windowHeight}",
"--default-background-color=00000000",
htmlFilePath
)
}
Expand Down
Loading