Zoom and crop YouTube videos to fill screen height

Removes letterboxing by cropping the left and right edges off of full-screen YouTube videos and zooming to fill the screen height.

目前為 2019-01-20 提交的版本,檢視 最新版本

// ==UserScript==
// @name Zoom and crop YouTube videos to fill screen height
// @version      0.5
// @description  Removes letterboxing by cropping the left and right edges off of full-screen YouTube videos and zooming to fill the screen height.
// @author       uxamend
// @namespace    Violentmonkey Scripts
// @match        https://www.youtube.com/*
// @exclude      https://www.youtube.com/ad_frame*
// @grant        none
// @run-at       document-idle
// @license      CC0-1.0
// @compatible   firefox version >=64 (older versions untested)
// @compatible   chrome version >=71 (older versions untested)
// ==/UserScript==

"use strict";

var debug_logging_on = false;
var debug_script_name = "Userscript: Zoom YouTube videos to fill screen height"

// For tall screens: Sets the narrowest aspect ratio that videos will ever be cropped to.
var min_cropped_aspect = 16/10; // Express as a fraction, e.g. 16/10, not 16:10.

// For very wide videos: The maximum proportion of video width to crop off
var max_crop_proportion = 1;

function debug_log(message) {
  if(debug_logging_on){
    console.log("[" + debug_script_name + "] " + message);
  }
}

// Define a mutation observer and callback to observe video element
function mo_callback(mutation_list, observer) {
  mutation_list.forEach((mutation) => {
    if(mutation.type == "attributes"){
      
      // We have to check whether zoom "should" be on, because the fullscreenchange event
      // may not be fast enough, in which case we will catch the mutations caused by
      // exiting full-screen.
      if(zoom_should_be_on()) {
        debug_log("Video element mutated.");
        set_zoom(mutation.target);
      } else {
        debug_log("Video element mutated but zoom should be off.");
        zoom_off();
      }
    }
  });
}

var mo = new MutationObserver(mo_callback);

function observe_video_mutations(video) {
  mo.observe(video, {"attributes" : true});
}

function set_zoom(video) {
  var vs = video.style;
  var w = window.innerWidth;
  var h = window.innerHeight;

  var video_aspect = video.videoWidth/video.videoHeight;
  var screen_aspect = w/h;

  // Don't zoom if the endscreen is showing
  if(!video.ended) {
    // Only zoom and crop videos that are wide enough to crop
    if(video_aspect > screen_aspect && video_aspect > min_cropped_aspect) {
      debug_log("Video is wider than screen and min_cropped_aspect. Setting zoom.");

      var vh = h; // height of video

      // Apply min_cropped_aspect constraint to video height
      if (min_cropped_aspect > screen_aspect) vh = w/min_cropped_aspect;

      var vw = video_aspect * vh; // width of video, including cropped portion

      // Apply max_crop_proportion constraint to video width
      if (w/vw < 1-max_crop_proportion) vw = w + vw * max_crop_proportion;

      var vt = (h-vh)/2; // top edge position of video
      var vl = (w-vw)/2; // left edge position of video

      debug_log("Screen dimensions: " + w + "x" + h + ".");
      debug_log("Calculated new video element dimensions: " + vw + "x" + vh + ", origin at " + vt + ", " + vl + ".");
      debug_log("(Underlying video stream resolution: " + video.videoWidth + "x" + video.videoHeight + ".)");
      debug_log("screen_aspect: " + screen_aspect + ".");
      debug_log("min_cropped_aspect: " + min_cropped_aspect + "." );
      debug_log("video_aspect: " + video_aspect + ".");
      debug_log("max_crop_proportion: " + max_crop_proportion + ".");

      // Note: This might appear to risk creating an endless loop via the mutation observer.
      // However, it should result in at most one superfluous execution of set_zoom().
      // If the first execution causes a mutation by changing the video element's dimensions,
      // then the second execution, if it is surplus to requirements, should set them to the
      // same values, resulting in no mutation and no third execution (until genuinely needed).
      vs.height = vh+"px";
      vs.width = vw+"px";
      vs.top = vt+"px";
      vs.left = vl+"px";

    } else {
      debug_log("Video is not wide enough to require zoom ("
                + video.videoWidth
                + "x"
                + video.videoHeight
                + "). Not setting zoom.");
    }
  } else {
    debug_log("Video has ended. Not setting zoom. (Otherwise we mess with the endscreen.)")
  }
}

function zoom_on() {
  debug_log("Turning zoom on.");
  var video = document.getElementsByClassName("html5-main-video")[0];
  set_zoom(video);
  observe_video_mutations(video);
}

function zoom_off() {
  debug_log("Turning zoom off.");
  mo.disconnect();
}

function zoom_should_be_on() {
  // Double 'not' to turn object presence into boolean
  return !(!(document.fullscreenElement));
}

function zoom_on_or_off() {
  if(zoom_should_be_on()) {
    setTimeout(zoom_on, 200);
  } else {
    zoom_off();
  }
}

function watch_for_fullscreen() {
  debug_log("Adding fullscreenchange event listener.")
  document.addEventListener(
    'fullscreenchange',
    function() {
      debug_log("Full-screen state changed.");
      zoom_on_or_off();
    }
  );
}

watch_for_fullscreen();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址