MediaTransformations.scala 3.93 KB
Newer Older
1
/*
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
2
 * Media Converter
3
 * Extracts media files from Fedora repository
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
4
 * Copyright (C) 2021  Memoriav
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

20
package ch.memobase
21
22
23

import org.apache.logging.log4j.scala.Logging

24
import java.io.IOException
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
25
import scala.util.Try
26

Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
27
28
29
/**
 * Contains functions used to transform specific media files
 */
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
30
object MediaTransformations extends Logging {
31

Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
32
  import sys.process._
33

34
  private def executeCommand(command: String): Try[Int] = Try {
35
    logger.debug(s"Execute: $command")
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
36
37
38
    val stderr = StringBuilder.newBuilder
    val errorCode = command ! ProcessLogger(logger.debug(_), stderr append _)
    if (errorCode > 0) {
39
40
41
      val errorMsg = s"application ${command.split(' ')(0)} exited with code $errorCode: ${stderr.toString()}"
      logger.warn(errorMsg)
      throw new IOException(errorMsg)
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
42
    } else {
43
      logger.debug("Command execution successful")
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
44
45
46
      errorCode
    }
  }
47

Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
48
  /**
49
   * Repacks audio files in a MP4 container and adds a moov atom at the beginning of the file
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
50
   *
51
   * @param sourceFilePath Path to the source file
52
   * @param destFile       Path to the final file
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
53
54
   * @return
   */
55
56
  def audioToMp4(sourceFilePath: String, destFile: String): Try[String] = {
    val externalCommand = s"ffmpeg -i $sourceFilePath -acodec copy -loglevel warning -hide_banner -y -movflags faststart $destFile"
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
57
    Try {
58
      executeCommand(externalCommand).get
59
      destFile
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
60
    }
61
  }
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
62

63
64
65
66
  /**
   * Creates an audio snippet used as a base for producing sonograms
   *
   * @param sourceFilePath Path to the source file
67
   * @param destFile       Path to the final file
68
69
70
   * @param duration       Duration of snippet (counts from beginning of track)
   * @return
   */
71
  def createAudioSnippet(sourceFilePath: String, destFile: String, duration: Int): Try[String] = {
72
73
74
75
    val minutes = (duration / 60) % 60
    val hours = (duration / 60 / 60) % 24
    val time = "%02d:%02d:%02d".format(hours, minutes, duration % 60)
    val copyStream = sourceFilePath.endsWith(".mp3")
76
    val externalCommand = s"ffmpeg -i $sourceFilePath ${if (copyStream) "-acodec copy"} -loglevel warning -hide_banner -y -t $time $destFile"
77
78
    Try {
      executeCommand(externalCommand)
79
      destFile
80
81
    }
  }
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

  /**
   * Creates an image thumbnail
   *
   * @param sourceFilePath Path to the source file
   * @param destFile       Path to the final file
   * @param width          Thumbnail's width. If None, width is set relative to height
   * @param height         Thumbnail's height If None, height is set relative to width
   * @return
   */
  def createImageThumbnail(sourceFilePath: String, destFile: String, width: Option[Int], height: Option[Int]): Try[String] = {
    val externalCommand =
      s"""convert \\
         |-filter Triangle \\
         |-define filter:support=2 \\
         |-thumbnail ${width.getOrElse("")}x${height.getOrElse("")} \\
         |-unsharp 0.25x0.08+8.3+0.045 \\
         |-dither None \\
         |-posterize 136 \\
         |-quality 82 \\
         |-define jpeg:fancy-upsampling=off \\
         |-define png:compression-filter=5 \\
         |-define png:compression-level=9 \\
         |-define png:compression-strategy=1 \\
         |-define png:exclude-chunk=all \\
         |-interlace none \\
         |$sourceFilePath \\
         |$destFile""".stripMargin
    Try {
      executeCommand(externalCommand)
      destFile
    }
  }
115
}