MediaTransformations.scala 3.64 KB
Newer Older
1
/*
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
2
 * Media Converter
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 * Extracts media files from Fedora repository
 * Copyright (C) 2020  Memoriav
 *
 * 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

Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
22
import java.io.IOException
23
import java.nio.file.Files
24
25
26

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

Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
27
import scala.util.Try
28

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

Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
34
  import sys.process._
35

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

Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
46
  /**
47
   * 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
48
   *
49
   * @param sourceFilePath Path to the source file
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
50
51
52
   * @param destFilePath   Path to the final file
   * @return
   */
53
  def audioToMp4(sourceFilePath: String, destFilePath: String): Try[String] = {
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
54
    val externalCommand = s"ffmpeg -i $sourceFilePath -acodec copy -loglevel warning -hide_banner -y -movflags faststart $destFilePath"
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
55
    Try {
56
      executeCommand(externalCommand).get
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
57
      destFilePath
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
58
    }
59
  }
Sebastian Schüpbach's avatar
Sebastian Schüpbach committed
60

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
  /**
   * Creates an audio snippet used as a base for producing sonograms
   *
   * @param sourceFilePath Path to the source file
   * @param destFilePath   Path to the final file
   * @param duration       Duration of snippet (counts from beginning of track)
   * @return
   */
  def createAudioSnippet(sourceFilePath: String, destFilePath: String, duration: Int): Try[String] = {
    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")
    val externalCommand = s"ffmpeg -i $sourceFilePath ${if (copyStream) "-acodec copy"} -loglevel warning -hide_banner -y -t $time $destFilePath"
    Try {
      executeCommand(externalCommand)
      destFilePath
    }
  }

81
82
83
84
85
86
87
88
89
90
  /**
   * Converts image file to jpeg2000
   *
   * @param sourceFilePath Path to the source file
   * @param destFilePath   Path to the final file
   * @return
   */
  def imageToJp2(sourceFilePath: String, destFilePath: String): Try[String] = Try {
    val intermediaryFile = Files.createTempFile("image-", ".tif")
    val imagemagickCommand = s"convert -format tif -compress none $sourceFilePath ${intermediaryFile.toString}"
91
92
93
    // scalastyle:off
    val kduCompressCommand = s"""kdu_compress -i ${intermediaryFile.toString} -o $destFilePath -rate 3 -flush_period 1024 -quiet Creversible=no Clevels=6 Clayers=6 Cprecincts={256,256},{256,256},{128,128} Corder=RPCL Cuse_sop=yes Cuse_eph=yes Cblk={64,64} ORGgen_plt=yes ORGtparts=R Stiles={512,512}"""
    // scalastyle:on
94
95
96
97
98
99
100
101
102
    try {
      executeCommand(imagemagickCommand).get
      executeCommand(kduCompressCommand).get
    } finally {
      Files.delete(intermediaryFile)
    }
    destFilePath
  }

103
}