1.YouTube視聴時間制限デスクトップアプリ

CustomTkinter

overall

・YouTubeを1日1時間までしか見れないようにしたい

・とりあえずWindows版だけでOK

・1時間を超えるとHostsファイルを編集してDNSを偽装し24時間経つまで見れないようにする

・Hostsファイルを編集するには管理者権限でアプリを起動させる必要がある

・Chrome拡張機能だとEdgeとかで見れてしまうのでデスクトップアプリにしたい

・アプリを起動しなくてもWindows起動時に自動起動するようにしたい

・視聴時間を監視するのでバックグラウンド機能が必要

※欲を言えば毎日のYouTube視聴時間を可視化したい

※欲を言えば、YouTubeを開くたびに最前面に問題が表示されて、その問題を正解しないとYouTubeが見れないようにしたい(勉強広告というアプリ名にする)

select programming language

Custom Tkinterでは「ブラウザで開いているタブのタイトルやURL」を取得できなかった → Chrome拡張機能に変更

Custom Tkinter (python)Electron (node.js)Chrome拡張機能(Edge拡張機能)
権限管理pythonのosやsubprocessで簡単node.jsではsudo-prompt等の外部モジュールが必要不要?
Hosts操作with open(…)など簡単node.jsでfsで編集できるが管理者権限がネック不要
実行サイズ数MB数百MB(Electronは重い)不明
タスクスケジューリングscheduleやthreading.Timerで簡単非同期処理で複雑不明
YouTube視聴時間の計測chrome.exeやmsedge.exeのウィンドウタイトルにYouTubeが含まれているかを定期チェック左に同じ正確に計測可能

失敗(CustomTkinter)

pip install customtkinter psutil
pip install pywin32   # win32guiを使う場合
import customtkinter as ctk
import os
import sys
import time
import datetime
import threading
import json
import winreg
import psutil
import win32gui

# =====
# 設定
# (使用時間データの保存先は %APPDATA% = C:\Users\ユーザ名\AppData\Roaming)
# (os.getenv()は環境変数Keyがあればその値を返す関数)
# =====
HOSTS_PATH = r"C:\Windows\System32\drivers\etc\hosts"
DOMAINS = ["youtube.com", "www.youtube.com", "m.youtube.com"]
USAGE_FILE = os.path.join(os.getenv("APPDATA"), "youtube_limiter.json")
DAILY_LIMIT = 3600
CHECK_INTERVAL = 5
usage_seconds = 0
last_active = None
stop_thread = False

# =====
# PC起動時に自動でこのアプリを起動させる
# =====
def add_to_startup():
    exe_path = sys.executable
    key_path = r"Software\Microsoft\Windows\CurrentVersion\Run"
    with winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path, 0, winreg.KEY_SET_VALUE) as key:
        winreg.SetValueEx(key, "YouTubeLimiter", 0, winreg.REG_SZ, exe_path)
    print("スタートアップ登録済み")


# =====
# 制限発動(hostsファイル編集)
# =====
def block_youtube():
    with open(HOSTS_PATH, "r+", encoding="utf-8") as f:
        content = f.read()
        for domain in DOMAINS:
            if domain not in content:
                f.write(f"\n127.0.0.1 {domain}")
    print("YouTubeをブロック中")


# =====
# 制限解除
# (書き込み専用で開くとファイルが空になる)
# =====
def unblock_youtube():
    with open(HOSTS_PATH, "r", encoding="utf-8") as f:
        lines = f.readlines()
    with open(HOSTS_PATH, "w", encoding="utf-8") as f:
        for line in lines:
            if not any(domain in line for domain in DOMAINS):
                f.write(line)
    print("YouTubeブロック解除")


# =====
# 使用時間データの読み込み
# =====
def load_usage():
    if not os.path.exists(USAGE_FILE):
        return {"date": str(datetime.date.today()), "seconds": 0}
    with open(USAGE_FILE, "r") as f:
        return json.load(f)


# =====
# 使用時間データの保存
# =====
def save_usage(data):
    with open(USAGE_FILE, "w") as f:
        json.dump(data, f)


# =====
# 使用時間データのリセット
# =====
def reset_daily_usage():
    data = {"date": str(datetime.date.today()), "seconds": 0}
    save_usage(data)
    unblock_youtube()
    print("使用時間をリセット")


# =====
# 【失敗】YouTubeを視聴しているか検出
# psutilではブラウザで開いているURLを取得できなかった
# =====
def is_youtube_active():
    for proc in psutil.process_iter(['name', 'cmdline']):
        try:
            name = (proc.info['name'] or "").lower()
            cmdline_list = proc.info.get('cmdline', [])
            if not isinstance(cmdline_list, (list, tuple)):
                continue  # None など iterable でない場合スキップ

            cmdline = " ".join(cmdline_list).lower()
            if any(browser in name for browser in ["brave", "chrome", "msedge", "firefox"]):
                if "youtube.com" in cmdline:
                    print(f"✅ YouTube検出: {name} ({cmdline})")
                    return True
        except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
            continue
    return False

# =====
# 【失敗】YouTubeを視聴しているか検出
# win32guiではブラウザのタブ名を取得できなかった
# =====
# def is_youtube_active():
#     def callback(hwnd, titles):
#         if win32gui.IsWindowVisible(hwnd):
#             title = win32gui.GetWindowText(hwnd)
#             if title:
#                 print(title)
#             if "YouTube" in title:
#                 titles.append(title)
#     titles = []
#     win32gui.EnumWindows(callback, titles)
#     return len(titles) > 0


# =====
# YouTube視聴時間の測定
# (psutilでChromeなどのプロセス一覧を調査し、コマンドラインに"youtube.com"を含むか確認)
# (ブラウザでYouTubeを開いているだけで加算されるため、実際に再生していなくてもカウントされる)
# =====
def monitor_time():
    global usage_seconds, last_active
    data = load_usage()
    usage_seconds = data["seconds"]

    while not stop_thread:
        today = str(datetime.date.today())
        if data["date"] != today:
            reset_daily_usage()
            data = load_usage()
            usage_seconds = 0
        
        if is_youtube_active():
            usage_seconds += CHECK_INTERVAL

        data["seconds"] = usage_seconds
        save_usage(data)

        if usage_seconds >= DAILY_LIMIT:
            block_youtube()
        else:
            unblock_youtube()
        
        time.sleep(CHECK_INTERVAL)


# =====
# GUI
# =====
app = ctk.CTk()
app.title("制限アプリ")
app.geometry("300x200")

label = ctk.CTkLabel(app, text="YouTube使用時間: 0秒 / 3600秒")
label.pack(pady=20)

def update_label():
    label.configure(text=f"YouTube使用時間: {usage_seconds}秒 / 3600秒")
    app.after(1000, update_label)

def on_close():
    global stop_thread
    stop_thread = True
    app.destroy()

reset_btn = ctk.CTkButton(app, text="リセット", command=reset_daily_usage)
reset_btn.pack(pady=10)

add_to_startup()
threading.Thread(target=monitor_time, daemon=True).start()
update_label()
# WM_DELETE_WINDOWはユーザーがアプリウィンドウの閉じるボタンを押すと発生するイベント
app.protocol("WM_DELETE_WINDOW", on_close)
app.mainloop()

pyinstaller

pyinstaller --noconsole --onefile --uac-admin main.py

pyinstallerでビルド時に管理者権限要求を付与します

–uac-admin によってアプリ実行時に「このアプリがデバイスに変更を加えることを許可しますか?」というUACが自動で表示されます

BACK