tf auto_crop
This commit is contained in:
@@ -9,7 +9,7 @@ DEFAULT_AC3_BANDWIDTH = "256"
|
||||
DEFAULT_DTS_BANDWIDTH = "320"
|
||||
DEFAULT_7_1_BANDWIDTH = "384"
|
||||
|
||||
DEFAULT_CROP_START = 60
|
||||
DEFAULT_CROP_LENGTH = 180
|
||||
DEFAULT_cut_start = 60
|
||||
DEFAULT_cut_length = 180
|
||||
|
||||
DEFAULT_OUTPUT_FILENAME_TEMPLATE = '{{ ffx_show_name }} - {{ ffx_index }}{{ ffx_index_separator }}{{ ffx_episode_name }}{{ ffx_indicator_separator }}{{ ffx_indicator }}'
|
||||
|
||||
@@ -30,6 +30,7 @@ from ffx.constants import DEFAULT_STEREO_BANDWIDTH, DEFAULT_AC3_BANDWIDTH, DEFAU
|
||||
from ffx.filter.quality_filter import QualityFilter
|
||||
from ffx.filter.preset_filter import PresetFilter
|
||||
|
||||
from ffx.filter.crop_filter import CropFilter
|
||||
from ffx.filter.nlmeans_filter import NlmeansFilter
|
||||
|
||||
from ffx.constants import VERSION
|
||||
@@ -329,7 +330,8 @@ def checkUniqueDispositions(context, mediaDescriptor: MediaDescriptor):
|
||||
|
||||
@click.option('--rearrange-streams', type=str, default="", help='Rearrange output streams order. Use format comma separated integers')
|
||||
|
||||
@click.option("--crop", is_flag=False, flag_value="default", default="none")
|
||||
@click.option("--crop", is_flag=False, flag_value="auto", default="none")
|
||||
@click.option("--cut", is_flag=False, flag_value="default", default="none")
|
||||
|
||||
@click.option("--output-directory", type=str, default='')
|
||||
|
||||
@@ -385,6 +387,8 @@ def convert(ctx,
|
||||
rearrange_streams,
|
||||
|
||||
crop,
|
||||
cut,
|
||||
|
||||
output_directory,
|
||||
|
||||
denoise,
|
||||
@@ -531,15 +535,15 @@ def convert(ctx,
|
||||
ctx.obj['logger'].debug(f"AC3 bitrate: {context['bitrates']['ac3']}")
|
||||
ctx.obj['logger'].debug(f"DTS bitrate: {context['bitrates']['dts']}")
|
||||
|
||||
|
||||
# Process crop parameters
|
||||
context['perform_crop'] = (crop != 'none')
|
||||
if context['perform_crop']:
|
||||
cTokens = crop.split(',')
|
||||
if cTokens and len(cTokens) == 2:
|
||||
context['crop_start'] = int(cTokens[0])
|
||||
context['crop_length'] = int(cTokens[1])
|
||||
ctx.obj['logger'].debug(f"Crop start={context['crop_start']} length={context['crop_length']}")
|
||||
#->
|
||||
# Process cut parameters
|
||||
context['perform_cut'] = (cut != 'none')
|
||||
if context['perform_cut']:
|
||||
cutTokens = cut.split(',')
|
||||
if cutTokens and len(cutTokens) == 2:
|
||||
context['cut_start'] = int(cutTokens[0])
|
||||
context['cut_length'] = int(cutTokens[1])
|
||||
ctx.obj['logger'].debug(f"Cut start={context['cut_start']} length={context['cut_length']}")
|
||||
|
||||
|
||||
tc = TmdbController() if context['use_tmdb'] else None
|
||||
@@ -551,6 +555,12 @@ def convert(ctx,
|
||||
presetKwargs = {PresetFilter.PRESET_KEY: preset}
|
||||
PresetFilter(**presetKwargs)
|
||||
|
||||
cf = None
|
||||
# if crop != 'none':
|
||||
if crop == 'auto':
|
||||
cropKwargs = {}
|
||||
cf = CropFilter(**cropKwargs)
|
||||
|
||||
denoiseKwargs = {}
|
||||
if denoise_strength:
|
||||
denoiseKwargs[NlmeansFilter.STRENGTH_KEY] = denoise_strength
|
||||
@@ -587,6 +597,11 @@ def convert(ctx,
|
||||
|
||||
mediaFileProperties = FileProperties(context, sourcePath)
|
||||
|
||||
|
||||
if not cf is None:
|
||||
cf.setArguments(**mediaFileProperties.findCropArguments())
|
||||
|
||||
|
||||
ssc = ShiftedSeasonController(context)
|
||||
|
||||
showId = mediaFileProperties.getShowId()
|
||||
|
||||
@@ -9,7 +9,7 @@ from ffx.track_codec import TrackCodec
|
||||
from ffx.video_encoder import VideoEncoder
|
||||
from ffx.process import executeProcess
|
||||
|
||||
from ffx.constants import DEFAULT_CROP_START, DEFAULT_CROP_LENGTH
|
||||
from ffx.constants import DEFAULT_cut_start, DEFAULT_cut_length
|
||||
|
||||
from ffx.filter.quality_filter import QualityFilter
|
||||
from ffx.filter.preset_filter import PresetFilter
|
||||
@@ -97,12 +97,12 @@ class FfxController():
|
||||
|
||||
def generateCropTokens(self):
|
||||
|
||||
if 'crop_start' in self.__context.keys() and 'crop_length' in self.__context.keys():
|
||||
cropStart = int(self.__context['crop_start'])
|
||||
cropLength = int(self.__context['crop_length'])
|
||||
if 'cut_start' in self.__context.keys() and 'cut_length' in self.__context.keys():
|
||||
cropStart = int(self.__context['cut_start'])
|
||||
cropLength = int(self.__context['cut_length'])
|
||||
else:
|
||||
cropStart = DEFAULT_CROP_START
|
||||
cropLength = DEFAULT_CROP_LENGTH
|
||||
cropStart = DEFAULT_cut_start
|
||||
cropLength = DEFAULT_cut_length
|
||||
|
||||
return ['-ss', str(cropStart), '-t', str(cropLength)]
|
||||
|
||||
@@ -211,7 +211,7 @@ class FfxController():
|
||||
|
||||
commandSequence += self.generateAudioEncodingTokens()
|
||||
|
||||
if self.__context['perform_crop']:
|
||||
if self.__context['perform_cut']:
|
||||
commandSequence += self.generateCropTokens()
|
||||
|
||||
commandSequence += self.generateOutputTokens(targetPath,
|
||||
@@ -241,7 +241,7 @@ class FfxController():
|
||||
|
||||
commandSequence += self.generateAudioEncodingTokens()
|
||||
|
||||
if self.__context['perform_crop']:
|
||||
if self.__context['perform_cut']:
|
||||
commandSequence += self.generateCropTokens()
|
||||
|
||||
commandSequence += self.generateOutputTokens(targetPath,
|
||||
@@ -271,7 +271,7 @@ class FfxController():
|
||||
if td.getCodec != TrackCodec.PNG:
|
||||
commandSequence1 += self.generateVP9Pass1Tokens(int(quality))
|
||||
|
||||
if self.__context['perform_crop']:
|
||||
if self.__context['perform_cut']:
|
||||
commandSequence1 += self.generateCropTokens()
|
||||
|
||||
commandSequence1 += FfxController.NULL_TOKENS
|
||||
@@ -300,7 +300,7 @@ class FfxController():
|
||||
|
||||
commandSequence2 += self.generateAudioEncodingTokens()
|
||||
|
||||
if self.__context['perform_crop']:
|
||||
if self.__context['perform_cut']:
|
||||
commandSequence2 += self.generateCropTokens()
|
||||
|
||||
commandSequence2 += self.generateOutputTokens(targetPath,
|
||||
|
||||
@@ -3,6 +3,8 @@ import os, re, json
|
||||
from .media_descriptor import MediaDescriptor
|
||||
from .pattern_controller import PatternController
|
||||
|
||||
from ffx.filter.crop_filter import CropFilter
|
||||
|
||||
from .process import executeProcess
|
||||
|
||||
from ffx.model.pattern import Pattern
|
||||
@@ -177,48 +179,8 @@ class FileProperties():
|
||||
|
||||
|
||||
|
||||
def findCropParams(self):
|
||||
"""Returns ffprobe stream data as array with elements according to the following example
|
||||
{
|
||||
"index": 4,
|
||||
"codec_name": "hdmv_pgs_subtitle",
|
||||
"codec_long_name": "HDMV Presentation Graphic Stream subtitles",
|
||||
"codec_type": "subtitle",
|
||||
"codec_tag_string": "[0][0][0][0]",
|
||||
"codec_tag": "0x0000",
|
||||
"r_frame_rate": "0/0",
|
||||
"avg_frame_rate": "0/0",
|
||||
"time_base": "1/1000",
|
||||
"start_pts": 0,
|
||||
"start_time": "0.000000",
|
||||
"duration_ts": 1421035,
|
||||
"duration": "1421.035000",
|
||||
"disposition": {
|
||||
"default": 1,
|
||||
"dub": 0,
|
||||
"original": 0,
|
||||
"comment": 0,
|
||||
"lyrics": 0,
|
||||
"karaoke": 0,
|
||||
"forced": 0,
|
||||
"hearing_impaired": 0,
|
||||
"visual_impaired": 0,
|
||||
"clean_effects": 0,
|
||||
"attached_pic": 0,
|
||||
"timed_thumbnails": 0,
|
||||
"non_diegetic": 0,
|
||||
"captions": 0,
|
||||
"descriptions": 0,
|
||||
"metadata": 0,
|
||||
"dependent": 0,
|
||||
"still_image": 0
|
||||
},
|
||||
"tags": {
|
||||
"language": "ger",
|
||||
"title": "German Full"
|
||||
}
|
||||
}
|
||||
"""
|
||||
def findCropArguments(self):
|
||||
""""""
|
||||
|
||||
# ffmpeg -i <input.file> -vf cropdetect -f null -
|
||||
ffprobeOutput, ffprobeError, returnCode = executeProcess(["ffmpeg", "-i",
|
||||
@@ -243,9 +205,19 @@ class FileProperties():
|
||||
|
||||
if crops:
|
||||
cropHistogram = sorted(crops, reverse=True)
|
||||
return cropHistogram[0]
|
||||
cropString = cropHistogram[0]
|
||||
|
||||
cropTokens = cropString.split('=')
|
||||
cropValueTokens = cropTokens[1]
|
||||
cropValues = cropValueTokens.split(':')
|
||||
return {
|
||||
CropFilter.OUTPUT_WIDTH_KEY: cropValues[0],
|
||||
CropFilter.OUTPUT_HEIGHT_KEY: cropValues[1],
|
||||
CropFilter.OFFSET_X_KEY: cropValues[2],
|
||||
CropFilter.OFFSET_Y_KEY: cropValues[3]
|
||||
}
|
||||
else:
|
||||
return ''
|
||||
return {}
|
||||
|
||||
|
||||
def getMediaDescriptor(self):
|
||||
|
||||
67
src/ffx/filter/crop_filter.py
Normal file
67
src/ffx/filter/crop_filter.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import itertools
|
||||
|
||||
from .filter import Filter
|
||||
|
||||
|
||||
class CropFilter(Filter):
|
||||
|
||||
IDENTIFIER = 'crop'
|
||||
|
||||
OUTPUT_WIDTH_KEY = 'output_width'
|
||||
OUTPUT_HEIGHT_KEY = 'output_height'
|
||||
OFFSET_X_KEY = 'x_offset'
|
||||
OFFSET_Y_KEY = 'y_offset'
|
||||
|
||||
# ffmpeg -i in.mp4 -vf "crop=out_w:out_h:x:y" out.mp4
|
||||
#
|
||||
# Where the options are as follows:
|
||||
#
|
||||
# use "-vf" or -"filter:v" - depending on your version of ffmpeg/avconv
|
||||
# out_w is the width of the output rectangle
|
||||
# out_h is the height of the output rectangle
|
||||
# x and y specify the top left corner of the output rectangle (coordinates start at (0,0) in the top left corner of the input)
|
||||
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
self.__outputWidth = int(kwargs.get(CropFilter.OUTPUT_WIDTH_KEY))
|
||||
self.__outputHeight = int(kwargs.get(CropFilter.OUTPUT_HEIGHT_KEY))
|
||||
self.__offsetX = int(kwargs.get(CropFilter.OFFSET_X_KEY))
|
||||
self.__offsetY = int(kwargs.get(CropFilter.OFFSET_Y_KEY))
|
||||
|
||||
super().__init__(self)
|
||||
|
||||
def setArguments(self,
|
||||
outputWidth: int,
|
||||
outputHeight: int,
|
||||
offsetX: int,
|
||||
offsetY: int):
|
||||
self.__outputWidth = int(outputWidth)
|
||||
self.__outputHeight = int(outputHeight)
|
||||
self.__offsetX = int(offsetX)
|
||||
self.__offsetY = int(offsetY)
|
||||
|
||||
def getPayload(self):
|
||||
|
||||
suffices = []
|
||||
|
||||
payload = {'identifier': CropFilter.IDENTIFIER,
|
||||
'parameters': {
|
||||
CropFilter.OUTPUT_WIDTH_KEY: self.__outputWidth,
|
||||
CropFilter.OUTPUT_HEIGHT_KEY: self.__outputHeight,
|
||||
CropFilter.OFFSET_X_KEY: self.__offsetX,
|
||||
CropFilter.OFFSET_Y_KEY: self.__offsetY
|
||||
},
|
||||
'suffices': [],
|
||||
'variant': f"C{self.__outputWidth}-{self.__outputHeight}-{self.__offsetX}-{self.__offsetY}",
|
||||
'tokens': ['crop='
|
||||
+ f"{self.__outputWidth}"
|
||||
+ f":{self.__outputHeight}"
|
||||
+ f":{self.__offsetX}"
|
||||
+ f":{self.__offsetY}"]}
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def getYield(self):
|
||||
yield self.getPayload()
|
||||
@@ -144,7 +144,7 @@ class NlmeansFilter(Filter):
|
||||
'suffices': suffices,
|
||||
'variant': f"DS{strength}-DP{patchSize}-DPC{chromaPatchSize}"
|
||||
+ f"-DR{researchWindow}-DRC{chromaResearchWindow}",
|
||||
'tokens': ['-vf', f"{filterName}=s={strength}"
|
||||
'tokens': [f"{filterName}=s={strength}"
|
||||
+ f":p={patchSize}"
|
||||
+ f":pc={chromaPatchSize}"
|
||||
+ f":r={researchWindow}"
|
||||
|
||||
Reference in New Issue
Block a user