Skip to content

Commit

Permalink
* Adding #81 auto crop feature
Browse files Browse the repository at this point in the history
* Fixing AVC always copied chapters
(Current bug with thumbnails not being created properly)
  • Loading branch information
cdgriffith committed Oct 21, 2020
1 parent 408e317 commit 80064d8
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 14 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
* Adding #84 pause / resume functionality (thanks to loungebob)
* Adding hover info for Audio and Subtitle tracks
* Adding confirm overwrite dialog if file already exists and is not empty
* Adding #81 auto crop feature
* Changing to explicitly set no-slow-firstpass for x265 bitrate runs
* Fixing AVC always copied chapters

## Version 3.1.0

Expand Down
7 changes: 2 additions & 5 deletions fastflix/encoders/avc_x264/command_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,14 @@ def build(
if side_data.cll:
pass

extra_data = "-map_chapters 0 " # -map_metadata 0 # safe to do for rotation?
pass_log_file = Path(temp_dir) / f"pass_log_file_{secrets.token_hex(10)}.log"

if bitrate:
command_1 = (
f"{beginning} -pass 1 "
f'-passlogfile "{pass_log_file}" -b:v {bitrate} -preset {preset} -an -sn -dn -f mp4 {null}'
)
command_2 = (
f'{beginning} -pass 2 -passlogfile "{pass_log_file}" ' f"-b:v {bitrate} -preset {preset} {extra_data}"
) + ending
command_2 = (f'{beginning} -pass 2 -passlogfile "{pass_log_file}" ' f"-b:v {bitrate} -preset {preset}") + ending
return [
Command(
re.sub("[ ]+", " ", command_1), ["ffmpeg", "output"], False, name="First pass bitrate", exe="ffmpeg"
Expand All @@ -81,7 +78,7 @@ def build(
]

elif crf:
command = (f"{beginning} -crf {crf} " f"-preset {preset} {extra_data}") + ending
command = (f"{beginning} -crf {crf} " f"-preset {preset} ") + ending
return [
Command(re.sub("[ ]+", " ", command), ["ffmpeg", "output"], False, name="Single pass CRF", exe="ffmpeg")
]
Expand Down
4 changes: 3 additions & 1 deletion fastflix/encoders/common/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,10 @@ def generate_filters(
)

filters = ",".join(filter_list)
if custom_filters:
if filters and custom_filters:
filters = f"{filters},{custom_filters}"
elif not filters and custom_filters:
filters = custom_filters

if burn_in_track is not None:
if filters:
Expand Down
24 changes: 23 additions & 1 deletion fastflix/flix.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,35 @@ def generate_filters(
def generate_thumbnail_command(self, source, output, filters, start_time=0):
start = ""
if start_time:
start = f"-ss {start_time}"
start = f""
return (
f'"{self.ffmpeg}" {start} -loglevel error -i "{source}" '
f" {filters} -an -y -map_metadata -1 "
f'-vframes 1 "{output}"'
)

def get_auto_crop(self, source, video_width, video_height, start_time):
command = f'"{self.ffmpeg}" -ss {start_time} -hide_banner -i "{source}" -vf cropdetect -vframes 10 -f null - '
output = self.execute(command)

width, height, x_crop, y_crop = None, None, None, None
for line in output.stderr.decode("utf-8").splitlines():
if line.startswith("[Parsed_cropdetect"):
w, h, x, y = [int(x) for x in line.rsplit("=")[1].split(":")]
if not width or (width and w < width):
width = w
if not height or (height and h < height):
height = h
if not x_crop or (x_crop and x > x_crop):
x_crop = x
if not y_crop or (y_crop and y > y_crop):
y_crop = y

if None in (width, height, x_crop, y_crop):
return 0, 0, 0, 0

return video_width - width - x_crop, video_height - height - y_crop, x_crop, y_crop

@staticmethod
def execute(command, work_dir=None, timeout=None):
logger.debug(f"running command: {command}")
Expand Down
45 changes: 38 additions & 7 deletions fastflix/widgets/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ def init_crop(self):
crop_box = QtWidgets.QGroupBox()
crop_box.setStyleSheet("QGroupBox{padding-top:17px; margin-top:-18px}")
crop_layout = QtWidgets.QVBoxLayout()
self.widgets.crop.top, crop_top_layout = self.build_hoz_int_field("Top ")
self.widgets.crop.top, crop_top_layout = self.build_hoz_int_field(" Top ")
self.widgets.crop.left, crop_hz_layout = self.build_hoz_int_field("Left ", right_stretch=False)
self.widgets.crop.right, crop_hz_layout = self.build_hoz_int_field(
" Right ", left_stretch=False, layout=crop_hz_layout
Expand All @@ -449,13 +449,28 @@ def init_crop(self):
label = QtWidgets.QLabel("Crop", alignment=(QtCore.Qt.AlignBottom | QtCore.Qt.AlignRight))
label.setStyleSheet("QLabel{color:#777}")
label.setMaximumHeight(40)
crop_bottom_layout.addWidget(label)

auto_crop = QtWidgets.QPushButton("Auto")
auto_crop.setMaximumHeight(40)

auto_crop.setFixedWidth(50)
auto_crop.setToolTip(
"Automatically detect black borders at current start time (or at 10% in if start time is 0)"
)
auto_crop.clicked.connect(self.get_auto_crop)

# crop_bottom_layout.addWidget(label)
l2 = QtWidgets.QVBoxLayout()
l2.addWidget(auto_crop, alignment=(QtCore.Qt.AlignTop | QtCore.Qt.AlignRight))
l2.addWidget(label, alignment=(QtCore.Qt.AlignBottom | QtCore.Qt.AlignRight))

crop_layout.addLayout(crop_top_layout)
crop_layout.addLayout(crop_hz_layout)
crop_layout.addLayout(crop_bottom_layout)

crop_box.setLayout(crop_layout)
outer = QtWidgets.QHBoxLayout()
outer.addLayout(crop_layout)
outer.addLayout(l2)
crop_box.setLayout(outer)

return crop_box

Expand Down Expand Up @@ -510,9 +525,9 @@ def build_hoz_int_field(
)

if not time_field:
widget.setFixedWidth(40)
widget.setFixedWidth(45)
else:
widget.setFixedWidth(60)
widget.setFixedWidth(65)
layout.addWidget(minus_button)
layout.addWidget(widget)
layout.addWidget(plus_button)
Expand Down Expand Up @@ -607,6 +622,16 @@ def save_file(self, extension="mkv"):
)
self.output_video_path_widget.setText(filename[0] if filename else "")

def get_auto_crop(self):
if not self.input_video or not self.initialized or self.loading_video:
return
start_pos = self.start_time or self.initial_duration // 10
r, b, l, t = self.flix.get_auto_crop(self.input_video, self.video_width, self.video_height, start_pos)
self.widgets.crop.top.setText(str(r))
self.widgets.crop.left.setText(str(b))
self.widgets.crop.right.setText(str(t))
self.widgets.crop.bottom.setText(str(l))

def build_crop(self):
top = int(self.widgets.crop.top.text())
left = int(self.widgets.crop.left.text())
Expand Down Expand Up @@ -755,6 +780,12 @@ def update_video_info(self):
self.page_update()
return

self.widgets.crop.top.setText("0")
self.widgets.crop.left.setText("0")
self.widgets.crop.right.setText("0")
self.widgets.crop.bottom.setText("0")
self.widgets.start_time.setText("0:00:00")

# TODO set width and height by video track
rotation = 0
if "rotate" in self.streams.video[0].get("tags", {}):
Expand Down Expand Up @@ -874,7 +905,7 @@ def generate_thumbnail(self, settings):

if settings.pix_fmt == "yuv420p10le" and self.pix_fmt in ("yuv420p10le", "yuv420p12le"):
settings.disable_hdr = True
filters = helpers.generate_filters(custom_filters='scale="min(320\\,iw):-1"', **settings)
filters = helpers.generate_filters(custom_filters="scale='min(320\\,iw):-1'", **settings)

preview_place = self.initial_duration // 10 if settings.start_time == 0 else settings.start_time

Expand Down

0 comments on commit 80064d8

Please sign in to comment.