TsMediaPage is a static web site generator for multimedia pages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

352 lines
12 KiB

#!/usr/bin/env ruby
# This file is part of TsMediaPage
# Copyright (C) 2017-2021 Moritz Strohm <ncc1988@posteo.de>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
require('open3')
require_relative('./Config.rb')
require_relative('./MediaPage.rb')
module TsMediaPage
##
# This class handles the conversion of media files.
# Each instance is responsible for converting one media file.
class MediaConverter
##
# The initialize method has two required parameters:
# +input_file_name+ and +output_path+.
# +input_file_name+ specifies the path to the input file
# while +output_path+ specifies the directory where the
# output file shall be placed.
# Depending on the media type the file name of the output
# file will differ so it cannot be specified from
# somewhere else.
# The parameters +title+, +categories+ and +description+
# may be placed in the media file's metadata, if appropriate.
def initialize(
input_file_name = ''
)
@config = TsMediaPage::Config.instance()
@input_file_name = input_file_name
#Input file name and output path are required!
if (!@input_file_name or !@config.output_path)
raise Error('Input file name and output path are required to convert media!')
end
@media_type = nil
#Detect the file type:
self.detectFileType()
if (!@media_type)
puts('Error: Media type for file ' + @input_file_name + ' cannot be detected!')
return false
end
@output_file_basename = File.basename(
@input_file_name,
File.extname(@input_file_name)
)
@output_file_path = @config.output_path +
'/' +
@output_file_basename
end
##
# Detects the type of the input file and sets the attribute
# media_type accordingly.
def detectFileType()
#Check, if the file is an image file:
if(@input_file_name.match(/(ogg|flac|opus|spx|mp3|m4a|aac|wav)$/))
#it is an audio file:
@media_type = 'a'
elsif(@input_file_name.match(/(ogv|webm|mp4|avi|mpg|dv|mkv)$/))
#it is a video file:
@media_type = 'v'
elsif (@input_file_name.match(/(jpg|svg|png)$/))
#it is an image file:
@media_type = 'i'
end
end
def getMediaType()
return @media_type
end
def createVideoThumbnail()
generation_necessary = false
thumbnail_file_name = @output_file_path + '.thumb.jpg'
if (File.exists?(thumbnail_file_name))
if (File.mtime(@input_file_name) > File.mtime(thumbnail_file_name))
generation_necessary = true
end
else
generation_necessary = true
end
if (generation_necessary)
#Extract one image from the middle of the video as thumbnail
#and convert the video file afterwards:
thumbnail_result = system(
'ffmpeg -loglevel quiet -ss 10 -i "' +
@input_file_name +
'" -vf "thumbnail" -frames:v 1 -s "' +
@config.thumbnail_size +
'" -y "' +
thumbnail_file_name + '"'
)
if (!thumbnail_result)
return false
end
end
return true
end
def createImageThumbnail()
generation_necessary = false
thumbnail_file_name = @output_file_path + '.thumb.jpg'
if (File.exists?(thumbnail_file_name))
if (File.mtime(@input_file_name) > File.mtime(thumbnail_file_name))
generation_necessary = true
end
else
generation_necessary = true
end
if (generation_necessary)
#Create thumbnail and convert image file:
thumbnail_result = system(
'convert "' +
@input_file_name +
'" -strip -resize "' +
@config.thumbnail_size +
'" -quality "' +
@config.thumbnail_quality +
'" "' +
thumbnail_file_name + '"'
)
if (!thumbnail_result)
return false;
end
end
return true
end
##
# Does the conversion of the input file with the appropriate
# file conversion method for the media type.
# If the output file is newer than the input file no conversion
# is made since the outpu file is up to date.
def convertFile()
profiles = []
if (@media_type == 'a')
profiles = @config.audio_profiles
elsif (@media_type == 'v')
self.createVideoThumbnail()
profiles = @config.video_profiles
elsif (@media_type == 'i')
self.createImageThumbnail()
profiles = @config.image_profiles
end
profiles.each {|profile|
output_file_name = @output_file_path + '-' + profile[:name]
if ((@media_type == 'a') or (@media_type == 'v'))
output_file_name += '.' + profile[:file_extension]
elsif (@media_type == 'i')
output_file_name += '.jpg'
end
output_file_name = File.expand_path(output_file_name)
#If the output file does not exist or
#if it is older than the input file,
#we must convert the file.
convert_file = false
if (!File.exist?(output_file_name))
convert_file = true
else
if (File.mtime(@input_file_name) > File.mtime(output_file_name))
convert_file = true
end
end
if (convert_file)
puts('Converting: ' +
@input_file_name +
' -> ' +
output_file_name
)
if (@media_type == 'a')
self.convertAudioFile(profile, output_file_name)
elsif (@media_type == 'v')
self.convertVideoFile(profile, output_file_name)
elsif (@media_type == 'i')
self.convertImageFile(profile, output_file_name)
end
else
puts ('NOTE: ' +
@input_file_name + ': ' +
output_file_name +
' is up to date!'
)
return true
end
}
end
def convertAudioFile(profile, output_file_name = '')
#IO.popen is used since ffmpeg's output may later
#be parsed to display a progress bar
ffmpeg = IO.popen(
[
'ffmpeg',
'-loglevel',
'quiet',
'-i',
@input_file_name,
'-vn',
'-codec:a',
profile[:codec],
'-ar',
profile[:samplerate],
'-ac',
profile[:channels],
'-b:a',
profile[:bitrate],
'-f',
profile[:container],
'-y',
output_file_name
],
:err => [:child, :out]
)
pid, status = Process.wait2(ffmpeg.pid)
ffmpeg.close()
return status.exitstatus == 0
end
def convertVideoFile(profile, output_file_name = '')
#Convert the video file.
#IO.popen is used since ffmpeg's output may later
#be parsed to display a progress bar
ffmpeg = IO.popen(
[
'ffmpeg',
'-loglevel',
'quiet',
'-i',
@input_file_name,
'-codec:v',
profile[:video_codec],
'-codec:a',
profile[:audio_codec],
'-s',
profile[:video_resolution],
'-r',
profile[:video_framerate],
'-b:v',
profile[:video_bitrate],
'-ar',
profile[:audio_samplerate],
'-ac',
profile[:audio_channels],
'-b:a',
profile[:audio_bitrate],
'-f',
profile[:container],
'-y',
output_file_name
],
:err => [:child, :out]
)
pid, status = Process.wait2(ffmpeg.pid)
ffmpeg.close()
if (status.exitstatus != 0)
puts (
'Error converting video file for profile ' +
profile[:name]
)
return false
end
end
def convertImageFile(profile, output_file_name = '')
@config.image_profiles.each { |profile|
#Convert has no progress output by default
#and for image files progress is not that important.
#That's why the system command is used here.
result = system(
'convert "' +
@input_file_name +
'" -strip -quality "' +
profile[:image_quality] +
'" "' +
output_file_name +
'"'
)
if (!result)
puts(
'Error converting image file for profile ' +
profile.name
)
return
end
}
end
##
# Handles the generation of media pages
# via the MediaPageGenerator class.
# Since MediaConverter has all the relevant
# data of the media file it can easily pass them
# to the MediaPageGenerator.
def generateMediaPage()
generator = TsMediaPage::MediaPage.new(
@input_file_name,
@output_file_basename,
@media_type
)
generator.createPage()
end
end
end