diff --git a/common_py/src/videoag_common/media_process/basic_targets.py b/common_py/src/videoag_common/media_process/basic_targets.py
index 5af86b9ea5b616369f4b0b072cab0db840adef8b..55252a816c6906e54b972f0f110013cbfc98034b 100644
--- a/common_py/src/videoag_common/media_process/basic_targets.py
+++ b/common_py/src/videoag_common/media_process/basic_targets.py
@@ -70,6 +70,7 @@ class SourceFileTargetProducer(SingleOutputTargetProducer["SourceMedium"]):
 
 class RescaleVideoTargetProducer(SingleInputTargetProducer, SingleOutputTargetProducer):
     target_vertical_resolution: int = json_field(min_value=1, max_value=10000)
+    crf: int | None = None
     
     @classmethod
     def get_type(cls) -> str:
@@ -96,7 +97,8 @@ class RescaleVideoTargetProducer(SingleInputTargetProducer, SingleOutputTargetPr
         return "rescale_video", {
             "input_file": input_medium.file.file_path,
             "output_file": output_file.file_path,
-            "target_vertical_resolution": self.target_vertical_resolution
+            "target_vertical_resolution": self.target_vertical_resolution,
+            "crf": self.crf
         }
 
 
diff --git a/common_py/src/videoag_common/media_process/jnode/file_jnode.py b/common_py/src/videoag_common/media_process/jnode/file_jnode.py
index 92fa48550376ce4a323c5367d00926d2b135b9d7..29b9ac5e0cb50347d7cadf0d31860f8edaa8b27e 100644
--- a/common_py/src/videoag_common/media_process/jnode/file_jnode.py
+++ b/common_py/src/videoag_common/media_process/jnode/file_jnode.py
@@ -252,6 +252,7 @@ _AUDIO_CODECS = [OutputCodec.OPUS, OutputCodec.FLAC, OutputCodec.AAC]
 class OutputStream(JsonDataClass):
     graph_id: str
     framerate: int | None = json_field(default=None, note="If not specified, ffmpeg will guess")
+    crf: int | None = None
     codec: OutputCodec
 
 
@@ -324,7 +325,12 @@ class OutputFileJNode(JGraphNode):
             if output_stream.framerate is not None:
                 if not isinstance(metadata, FVideoStreamMetadata):
                     raise ValueError(f"Cannot set output framerate for non-video stream {output_stream.graph_id}")
-                out_args["r"] = output_stream.framerate
+                out_args["r"] = output_stream.framerate#
+            
+            if output_stream.crf is not None:
+                if not isinstance(metadata, FVideoStreamMetadata):
+                    raise ValueError(f"Cannot set output crf for non-video stream {output_stream.graph_id}")
+                out_args["crf"] = output_stream.crf
             
             streams.append((metadata.fid, out_args))
         
diff --git a/example_media_process.json b/example_media_process.json
index 04ed13a85358932a3d34fff28048bf22d0f49759..4877667998e34b88a3dea69edebceb0baedf02a1 100644
--- a/example_media_process.json
+++ b/example_media_process.json
@@ -205,7 +205,8 @@
                     {
                         "graph_id": "video_with_inoutro_logo",
                         "codec": "av1",
-                        "framerate": 50
+                        "framerate": 50,
+                        "crf": 23
                     },
                     {
                         "graph_id": "audio_with_inoutro",
@@ -218,5 +219,5 @@
         ]
     },
     {"type": "sample_thumbnail", "timestamp_percent": 25, "input_id": "processed_video", "output_id": "thumbnail"},
-    {"type": "rescale_video", "target_vertical_resolution": 720, "input_id": "processed_video", "output_id": "video_720"}
+    {"type": "rescale_video", "target_vertical_resolution": 720, "crf": 23, "input_id": "processed_video", "output_id": "video_720"}
 ], "publish_target_ids": ["processed_video", "thumbnail", "video_720"], "publish_wait_for_full_process": true}
\ No newline at end of file
diff --git a/job_controller/jobs/rescale_video/job.py b/job_controller/jobs/rescale_video/job.py
index 39d32114ca5ccbc7d261cbde0edd6c8680acbcd4..ab0846f99026b72c0fb5576b7e30a12ab5ef3990 100644
--- a/job_controller/jobs/rescale_video/job.py
+++ b/job_controller/jobs/rescale_video/job.py
@@ -4,7 +4,7 @@ import subprocess
 from pathlib import Path
 
 from videoag_common.ffmpeg import get_file_extension, FFProbe, FFProbeVideoStream
-from videoag_common.miscellaneous import CJsonObject, MAX_VALUE_SINT32
+from videoag_common.miscellaneous import CJsonObject, MAX_VALUE_SINT32, MIN_VALUE_SINT32
 
 logger = logging.getLogger(__name__)
 
@@ -17,6 +17,7 @@ def execute(database, own_job_id, input_data: CJsonObject):
     output_file = _DATA_DIR.joinpath(input_data.get_string("output_file", max_length=None))
     tmp_output_file = Path("/tmp/job_output." + get_file_extension(output_file))
     target_vertical_resolution = input_data.get_int("target_vertical_resolution", min_value=0, max_value=MAX_VALUE_SINT32)
+    crf = input_data.get_int("crf", min_value=MIN_VALUE_SINT32, max_value=MAX_VALUE_SINT32, optional=True)
     
     if not input_file.exists() or not input_file.is_file():
         raise ValueError(f"Input file {input_file} doesn't exist")
@@ -51,6 +52,8 @@ def execute(database, own_job_id, input_data: CJsonObject):
                 raise ValueError(f"Unable to chose encoder for unknown codec '{codec}'")
             cmd.extend([f"-filter:{i}", f"scale=height={target_vertical_resolution}:width=-1"])
             cmd.extend([f"-c:{i}", encoder])
+            if crf is not None:
+                cmd.extend([f"-crf:{i}", str(crf)])
     
     cmd.extend(["-c:a", "copy"])