commit fe6c54aabc61136ed01058a64622f6c6cadb6948
parent 34692f6596bbc144e19ad80aca6f81123cb22b0d
Author: minerva-jupiter <ryouturn@gmail.com>
Date: Sun, 24 May 2026 11:17:25 +0900
feat: add documentation and video processing utilities
- Add Y-shirt sewing instructions (Y-shirt_Sewing_Instructions.md).
- Implement video similarity detection (similar_video.py) and image noise generation (rand-noise-image.py).
- Add AI-based image upscaling script (upscale_image.py) and Nushell script for audio conversion (wav2flac.nu).
- Improve `mov2mp4.py` with cross-platform file path handling, case-insensitive extension support, and updated batch configuration.
Diffstat:
9 files changed, 462 insertions(+), 65 deletions(-)
diff --git a/Y-shirt_Sewing_Instructions.md b/Y-shirt_Sewing_Instructions.md
@@ -0,0 +1,84 @@
+# Yシャツ縫製手順書(右前身頃ファスナー仕様)
+
+布の切断(裁断)が完了した状態から、完成に至るまでの詳細な縫製手順である。
+右前身頃に大型ファスナーを搭載する変則的な仕様に合わせ、平面の状態でファスナー処理を完了させる効率的な工程で構成している。
+
+---
+
+## 1. パーツの下準備(芯地貼り・アイロン)
+
+立体的な縫製に移る前に、すべてのパーツに芯地を貼り、アイロンで折り目をつけて成形する。
+
+### 1-1. 接着芯の貼付
+* **表衿・台衿:** 表衿全体、および台衿(表・裏の一方または両方)に接着芯を貼る。
+* **カフス・剣ボロ:** 表カフス、および剣ボロの表側に接着芯を貼る。
+* **表前立て:** 左前身頃の表前立て部分(または別布の前立て)に接着芯を貼る。
+* **ファスナー取付位置(重要):** 右前身頃のファスナーを縫い付ける位置の裏側に、生地の伸びやヨレを防ぐため、あらかじめ幅広の接着芯を貼って完全に補強する。
+
+### 1-2. アイロンによる折り込み
+* **表前立て(左前身頃):** 仕様に合わせ、前立ての端を規定の幅でアイロンで折る。
+* **右前身頃(ファスナー側):** ファスナーを挟み込む、または叩きつけるための端処理(三つ折りなど)に合わせてアイロンで折る。
+* **カフス・台衿:** 身頃や袖に縫い付ける側の縫い代を、あらかじめ片方だけアイロンで内側に折っておく。
+
+---
+
+## 2. 部分縫い(各パーツの組み立て・ファスナー取付)
+
+身頃が完全に平らな状態で、各パーツの組み立ておよび前立て・ファスナーの処理を行う。
+
+### 2-1. 前立て処理および右前身頃へのファスナー取付
+1. **左前身頃(表前立て)の縫製:** 折り込んだ前立ての両端にステッチをかけ、左前立てを仕上げる。
+2. **右前身頃へのファスナー取付:** * 芯地で補強した右前身頃のファスナー位置に、ファスナーを正確に配置する。
+ * 片おさえ(ファスナー押え)を使用し、ズレや波打ちがないよう注意しながら縫い付ける。
+ * ※上端(台衿との境界)および下端(裾の三つ折り位置)の縫い代を計算し、ファスナーのエレメント(務歯)が干渉しない位置に配置する。必要に応じて上端のエレメントを抜いておく。
+3. **左右のバランス確認:** 左前身頃と右前身頃を重ね合わせ、襟ぐりや裾のラインが完全に一致しているか確認し、仮止め等で位置を確定させる。
+
+### 2-2. 衿(カラー)の作成
+1. **衿羽の縫い合わせ:** 表衿と裏衿を中表に合わせ、外周(上端と左右)を縫い合わせる。角の縫い代を落として表に返し、形を整えて周囲にステッチをかける。
+2. **台衿への挟み込み:** 完成した衿羽を、2枚の台衿(表・裏)の間に挟み込む。
+3. **台衿の縫い合わせ:** 台衿の上側のカーブを縫い合わせ、表に返してアイロンで整える(下端は身頃に取り付けるため開けておく)。
+
+### 2-3. 袖口(剣ボロ)の処理
+1. **スリットの処理:** 袖口のあき(スリット)を入れ、剣ボロ(表側)と下ボロ(裏側)をそれぞれ縫い付ける。
+2. **剣ボロの叩きつけ:** 剣ボロの先端を五角形に折り込み、袖の表側に叩きつけてステッチで固定する。あきの止まり部分(カンヌキ位置)を四角やバツ印のステッチで補強縫いする。
+
+### 2-4. カフスの作成
+1. **カフス布の縫い合わせ:** カフス布2枚(表・裏)を中表に合わせ、外周(下端と左右)を縫い合わせる。
+2. **成形:** 表に返し、形を整えて周囲にステッチをかける(上端は袖口に縫い付けるため開けておく)。
+
+---
+
+## 3. まとめ縫い(身頃の組み立て・合体)
+
+各パーツを身頃と結合し、立体的に組み上げていく。右身頃のファスナーの重みで生地が引っ張られやすいため、地縫いの際は常に平らな状態を意識する。
+
+### 3-1. ヨークと身頃の縫い合わせ
+1. **後身頃の処理:** 後身頃にタックやギャザーがある場合は、あらかじめ畳んで仮止めする。
+2. **後身頃とヨークの結合:** 後身頃を2枚のヨーク(表ヨーク・裏ヨーク)で挟み込み、3枚一緒に縫い合わせる(地縫い)。
+3. **前身頃とヨークの結合(どんでん返し):** ヨークを上に起こし、前身頃の肩とヨーク(表・裏)を縫い合わせる。一般的なシャツの手法(どんでん返し)を用い、肩の縫い代をヨークの内部に完全に格納する。
+4. **コバステッチ:** ヨークの端(後身頃側・肩側)にステッチをかける。
+
+### 3-2. 衿の取り付け
+1. **台衿(外側)の縫い付け:** 身頃の襟ぐりに、作成済みの台衿(外側・表側)を縫い合わせる。
+2. **台衿(内側)の仕上げ:** 内側の台衿の縫い代を被せ、台衿の周囲にぐるりと落としミシン、またはコバステッチをかけて固定する。
+
+### 3-3. 袖の取り付け
+1. **アームホールの縫合:** 身頃を広げた状態で、肩口(アームホール)に袖を縫い合わせる。
+2. **端処理:** 縫い代は折り伏せ縫い、または巻伏せ縫いで処理する。
+
+### 3-4. 袖下から脇縫い
+1. **一気縫い:** 袖口から袖下、脇の下を通って裾に至るラインを、中表で一気に縫い合わせる。
+2. **端処理:** 同様に折り伏せ縫い等の手法で端を処理する。
+
+### 3-5. カフスの取り付け
+1. **袖口の仮止め:** 袖口のギャザーやタックを寄せて仮止めする。
+2. **カフスの縫合:** 袖口に作成済みのカフスを差し込み、表側から縫い合わせる。
+3. **裏側の仕上げ:** 内側のカフスの裏側を被せ、ステッチをかけて仕上げる。
+
+---
+
+## 4. 仕上げ
+
+1. **裾の処理:** 裾のカーブを三つ折り(またはヘム引き仕様)にし、ステッチをかける。右前身頃のファスナー下端との干渉に注意し、綺麗に収める。
+2. **ボタンホールとボタン:** 左前立て、台衿、カフス、剣ボロ(必要に応じて)にボタンホールを開け、対応する位置にボタンを縫い付ける。
+3. **最終プレス:** 全体の糸くずを入念に取り除き、アイロンでプレスして完成となる。
diff --git a/__pycache__/mov2mp4.cpython-312.pyc b/__pycache__/mov2mp4.cpython-312.pyc
Binary files differ.
diff --git a/batch_mov2mp4.py b/batch_mov2mp4.py
@@ -47,10 +47,10 @@ def run_batch_conversion(parent_dir, start_range, end_range):
# --- 設定項目 ---
# 親フォルダのパス
-PARENT_FOLDER = r"E:\Pictures\他人作品\ホロ"
+PARENT_FOLDER = r"E:\Pictures\他人作品\ホロ\aliceholic13"
# 処理対象のフォルダ名の範囲
-START_DIR = "2025010601"
-END_DIR = "2026042405"
+START_DIR = "1322528"
+END_DIR = "3035118"
if __name__ == "__main__":
run_batch_conversion(PARENT_FOLDER, START_DIR, END_DIR)
diff --git a/mov2mp4.py b/mov2mp4.py
@@ -1,3 +1,4 @@
+
import glob
import json
import os
@@ -5,9 +6,6 @@ import subprocess
def get_video_info(file_path):
- """
- ffprobe を使用して動画のコーデック名とビットレートを取得します。
- """
cmd = [
"ffprobe",
"-v",
@@ -31,52 +29,47 @@ def get_video_info(file_path):
video_stream = data.get("streams", [{}])[0]
codec_name = video_stream.get("codec_name", "unknown")
- # ビットレートは stream か format のいずれかから取得を試みる
bitrate = video_stream.get("bit_rate")
if not bitrate or bitrate == "N/A":
bitrate = data.get("format", {}).get("bit_rate")
return codec_name, int(bitrate) if bitrate and bitrate != "N/A" else None
except Exception as e:
- print(f" ⚠️ 動画情報の取得に失敗しました: {e}")
+ print(f" ⚠️ 動画情報の取得に失敗しました: {e}")
return "unknown", None
def convert_mov_to_mp4_h265_nvenc(input_dir, output_dir=None):
- # 入力フォルダーの存在を確認
if not os.path.isdir(input_dir):
print(f"エラー: 入力フォルダー '{input_dir}' が見つかりません。")
return
- # 出力フォルダーを設定。指定がなければ入力フォルダーと同じ
if output_dir is None:
output_dir = input_dir
- # 出力フォルダーが存在しない場合は作成
if not os.path.exists(output_dir):
os.makedirs(output_dir)
print(f"出力フォルダー '{output_dir}' を作成しました。")
- # 動画ファイルを検索 (.mov と .mp4)
video_files = []
- for ext in ["*.mov", "*.mp4"]:
+ # 拡張子の大文字小文字を網羅するため、globの検索パターンを修正
+ for ext in ["*.mov", "*.MOV", "*.mp4", "*.MP4", "*.mkv", "*.MKV"]:
video_files.extend(glob.glob(os.path.join(input_dir, ext)))
if not video_files:
print(f"'{input_dir}' 内に動画ファイルが見つかりませんでした。")
return
- video_files.sort()
+ # 重複してヒットする可能性を排除
+ video_files = sorted(list(set(os.path.abspath(f) for f in video_files)))
print(f"--- 変換処理を開始します (合計 {len(video_files)} ファイル) ---")
converted_count = 0
- # スキップ判定用のしきい値 (5Mbps)
- BITRATE_THRESHOLD = 5_000_000
+ BITRATE_THRESHOLD = 1_000_000
for input_file_path in video_files:
file_name_without_ext, ext = os.path.splitext(os.path.basename(input_file_path))
- # 入力がmp4の場合、一時的なファイル名を使用して衝突を避ける
is_mp4_input = ext.lower() == ".mp4"
if is_mp4_input:
output_file_path = os.path.join(
@@ -84,33 +77,28 @@ def convert_mov_to_mp4_h265_nvenc(input_dir, output_dir=None):
)
else:
output_file_path = os.path.join(output_dir, file_name_without_ext + ".mp4")
+
+ output_file_path = os.path.abspath(output_file_path)
print(f"\n🔍 解析中: '{os.path.basename(input_file_path)}' ...")
codec, bitrate = get_video_info(input_file_path)
- # 判定1: 既にHEVC(H.265)か
if codec in ["hevc", "h265"]:
- print(f" ⏭️ スキップ: 既に HEVC コーデックです。")
+ print(f" ⏭️ スキップ: 既に HEVC コーデックです。")
continue
- # 判定2: ビットレートが低すぎないか
if bitrate and bitrate < BITRATE_THRESHOLD:
print(
- f" ⏭️ スキップ: ビットレートが低いため変換のメリットがありません ({bitrate / 1_000_000:.2f} Mbps < {BITRATE_THRESHOLD / 1_000_000} Mbps)"
+ f" ⏭️ スキップ: ビットレートが低いため変換のメリットがありません ({bitrate / 1_000_000:.2f} Mbps < {BITRATE_THRESHOLD / 1_000_000} Mbps)"
)
continue
- # 出力先の最終的なファイル名が既に存在するかチェック(入力ファイル自体は除く)
- final_output_path = os.path.join(output_dir, file_name_without_ext + ".mp4")
- if os.path.exists(final_output_path) and os.path.abspath(
- final_output_path
- ) != os.path.abspath(input_file_path):
- print(
- f"\n❌ エラー: 出力予定のファイル '{final_output_path}' が既に存在します。"
- )
- print(
- "既存のファイルを上書きしないよう、このファイルの処理をスキップします。"
- )
+ final_output_path = os.path.abspath(os.path.join(output_dir, file_name_without_ext + ".mp4"))
+
+ # Windowsの仕様に合わせ、比較時にすべて小文字化して判定する
+ if os.path.exists(final_output_path) and final_output_path.lower() != input_file_path.lower():
+ print(f"\n❌ エラー: 出力予定のファイル '{final_output_path}' が既に存在します。")
+ print("既存のファイルを上書きしないよう、このファイルの処理をスキップします。")
continue
ffmpeg_command = [
@@ -124,9 +112,7 @@ def convert_mov_to_mp4_h265_nvenc(input_dir, output_dir=None):
output_file_path,
]
- print(
- f"\n✅ 変換中: '{os.path.basename(input_file_path)}' -> '{os.path.basename(output_file_path)}'"
- )
+ print(f"\n✅ 変換中: '{os.path.basename(input_file_path)}' -> '{os.path.basename(output_file_path)}'")
try:
subprocess.run(
@@ -136,61 +122,42 @@ def convert_mov_to_mp4_h265_nvenc(input_dir, output_dir=None):
stderr=subprocess.PIPE,
encoding="utf-8",
)
- print(" ✨ 変換成功。")
+ print(" ✨ 変換成功。")
- # サイズ比較と整理
input_size = os.path.getsize(input_file_path)
output_size = os.path.getsize(output_file_path)
- # 閾値(元のサイズの90%未満になっていれば採用)
THRESHOLD = 0.9
ratio = output_size / input_size if input_size > 0 else 1
if ratio < THRESHOLD:
- # 元ファイルを削除
os.remove(input_file_path)
- # 入力がmp4だった場合は一時ファイルを本来の名前にリネーム
+ # すでに小文字のfinal_output_pathが存在し、それが大文字のinput_file_pathであった場合は
+ # os.removeで消えているため、安全にrename可能
if is_mp4_input:
os.rename(output_file_path, final_output_path)
- print(f" 🗑️ サイズが十分に削減されたため、元ファイルを整理しました。")
- print(
- f" ({input_size:,} bytes -> {output_size:,} bytes, 削減率: {1 - ratio:.1%})"
- )
+ print(f" 🗑️ サイズが十分に削減されたため、元ファイルを整理しました。")
+ print(f" ({input_size:,} bytes -> {output_size:,} bytes, 削減率: {1 - ratio:.1%})")
converted_count += 1
else:
- # 削減が不十分なら変換後(一時)ファイルを削除
os.remove(output_file_path)
- print(
- f" ⚠️ サイズ削減が不十分なため、変換後ファイルを破棄し元ファイルを維持します。"
- )
+ print(f" ⚠️ サイズ削減が不十分なため、変換後ファイルを破棄し元ファイルを維持します。")
print(f" (比率: {ratio:.1%} / 閾値: {THRESHOLD:.1%})")
except subprocess.CalledProcessError as e:
- print(f" ❌ エラーが発生しました: '{os.path.basename(input_file_path)}'")
- print(
- f" FFmpeg出力:\n{e.stderr}..."
- ) # エラーメッセージの最初500文字を表示
+ print(f" ❌ エラーが発生しました: '{os.path.basename(input_file_path)}'")
+ print(f" FFmpeg出力:\n{e.stderr}...")
except FileNotFoundError:
- print(
- " ❌ エラー: FFmpegがインストールされていないか、PATHが通っていません。"
- )
- return # FFmpegが見つからない場合は処理を中断
+ print(" ❌ エラー: FFmpegがインストールされていないか、PATHが通っていません。")
+ return
print(f"\n--- 処理完了: {converted_count} 個のファイルが正常に変換されました ---")
-# --- 以下を convert.py の一番下に追加してください ---
-
-# 変換対象のフォルダーパス
-INPUT_FOLDER = r"E:\Pictures\他人作品\ホロ"
-
-# 変換後のファイルを保存するフォルダーパス (NoneにするとINPUT_FOLDERと同じになります)
+INPUT_FOLDER = r"E:\Pictures\他人作品\ホロ\temp"
OUTPUT_FOLDER = None
-# OUTPUT_FOLDER = r"E:\Pictures\他人作品\ホロ\2025112602"
if __name__ == "__main__":
- # Windowsの場合、パスは r"C:\Users\User\Videos" のように記述すると安全です
- # Mac/Linuxの場合、パスは "/Users/user/Movies/Source" のように記述します
convert_mov_to_mp4_h265_nvenc(INPUT_FOLDER, OUTPUT_FOLDER)
diff --git a/output_espcn.jpg b/output_espcn.jpg
Binary files differ.
diff --git a/rand-noise-image.py b/rand-noise-image.py
@@ -0,0 +1,21 @@
+
+import random
+from PIL import Image
+
+def change_random_pixels(image_path, output_path, iterations, color_hex="#2e1b1e"):
+ color = tuple(int(color_hex.lstrip('#')[i:i+2], 16) for i in (0, 2, 4))
+
+ with Image.open(image_path) as img:
+ img = img.convert("RGB")
+ pixels = img.load()
+ width, height = img.size
+
+ for _ in range(iterations):
+ x = random.randint(0, width - 1)
+ y = random.randint(0, height - 1)
+ pixels[x, y] = color
+
+ img.save(output_path)
+
+if __name__ == "__main__":
+ change_random_pixels("input.png", "output.png", 200000)
diff --git a/similar_video.py b/similar_video.py
@@ -0,0 +1,261 @@
+import os
+import json
+from pathlib import Path
+import cv2
+import numpy as np
+from PIL import Image
+import imagehash
+import ctypes
+from ctypes import wintypes
+
+def get_video_properties(video_path, root_path):
+ cap = cv2.VideoCapture(str(video_path))
+ if not cap.isOpened():
+ return {}
+
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
+ fps = cap.get(cv2.CAP_PROP_FPS)
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
+
+ duration = total_frames / fps if fps > 0 else 0
+ file_size_mb = os.path.getsize(video_path) / (1024 * 1024)
+
+ cap.release()
+
+ try:
+ rel_path = str(video_path.relative_to(root_path))
+ except ValueError:
+ rel_path = str(video_path)
+
+ return {
+ 'rel_path': rel_path,
+ 'resolution': f"{width}x{height}",
+ 'duration': f"{duration:.2f}s",
+ 'fps': f"{fps:.2f}",
+ 'size': f"{file_size_mb:.2f} MB"
+ }
+
+def calculate_video_phash(video_path, sample_frames=10):
+ cap = cv2.VideoCapture(str(video_path))
+ if not cap.isOpened():
+ return None
+
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
+ if total_frames <= 0:
+ cap.release()
+ return None
+
+ frame_indices = np.linspace(0, total_frames - 1, sample_frames, dtype=int)
+ hashes = []
+
+ for idx in frame_indices:
+ cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
+ ret, frame = cap.read()
+ if not ret:
+ continue
+
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
+ pil_img = Image.fromarray(frame_rgb)
+
+ try:
+ hash_val = imagehash.phash(pil_img)
+ hashes.append(hash_val)
+ except Exception:
+ continue
+
+ cap.release()
+ return hashes
+
+def load_or_calculate_hashes(root_dir, video_paths, sample_frames=10):
+ cache_path = Path(root_dir) / ".video_phash_cache.json"
+ cache_data = {}
+
+ if cache_path.exists():
+ try:
+ with open(cache_path, "r", encoding="utf-8") as f:
+ cache_data = json.load(f)
+ except Exception:
+ pass
+
+ video_hashes = {}
+ updated_cache = {}
+ cache_changed = False
+
+ for path in video_paths:
+ try:
+ stat = path.stat()
+ mtime = stat.st_mtime
+ size = stat.st_size
+ except Exception:
+ continue
+
+ str_path = str(path)
+
+ if str_path in cache_data:
+ cached = cache_data[str_path]
+ if cached.get('mtime') == mtime and cached.get('size') == size:
+ if cached['hashes']:
+ video_hashes[path] = [imagehash.hex_to_hash(h) for h in cached['hashes']]
+ updated_cache[str_path] = cached
+ continue
+
+ hashes = calculate_video_phash(path, sample_frames)
+
+ hash_strings = [str(h) for h in hashes] if hashes else []
+ if hashes:
+ video_hashes[path] = hashes
+
+ updated_cache[str_path] = {
+ 'mtime': mtime,
+ 'size': size,
+ 'hashes': hash_strings
+ }
+ cache_changed = True
+
+ if len(cache_data) != len(updated_cache):
+ cache_changed = True
+
+ if cache_changed:
+ try:
+ with open(cache_path, "w", encoding="utf-8") as f:
+ json.dump(updated_cache, f, indent=2, ensure_ascii=False)
+ except Exception:
+ pass
+
+ return video_hashes
+
+def open_in_explorer(file_paths):
+ if not file_paths:
+ return
+
+ shell32 = ctypes.windll.shell32
+ ole32 = ctypes.windll.ole32
+
+ ole32.CoInitialize(None)
+
+ dir_path = str(file_paths[0].parent)
+
+ # 関数の引数と戻り値の型を厳密に定義
+ ILCreateFromPathW = shell32.ILCreateFromPathW
+ ILCreateFromPathW.restype = ctypes.c_void_p
+ ILCreateFromPathW.argtypes = [wintypes.LPCWSTR]
+
+ ILFree = shell32.ILFree
+ ILFree.argtypes = [ctypes.c_void_p]
+
+ SHOpenFolderAndSelectItems = shell32.SHOpenFolderAndSelectItems
+ SHOpenFolderAndSelectItems.restype = ctypes.HRESULT
+ SHOpenFolderAndSelectItems.argtypes = [ctypes.c_void_p, wintypes.UINT, ctypes.c_void_p, wintypes.DWORD]
+
+ dir_pidl = ILCreateFromPathW(dir_path)
+ if not dir_pidl:
+ ole32.CoUninitialize()
+ return
+
+ file_pidls = []
+ for p in file_paths:
+ pidl = ILCreateFromPathW(str(p))
+ if pidl:
+ file_pidls.append(pidl)
+
+ if file_pidls:
+ # c_void_p の配列として正確に確保
+ pidl_array = (ctypes.c_void_p * len(file_pidls))(*file_pidls)
+ SHOpenFolderAndSelectItems(dir_pidl, len(file_pidls), ctypes.byref(pidl_array), 0)
+
+ for pidl in file_pidls:
+ ILFree(pidl)
+
+ ILFree(dir_pidl)
+ ole32.CoUninitialize()
+
+def group_similar_videos(video_hashes, threshold=15):
+ paths = list(video_hashes.keys())
+ num_videos = len(paths)
+
+ parent_map = {p: p for p in paths}
+
+ def find(p):
+ if parent_map[p] == p:
+ return p
+ parent_map[p] = find(parent_map[p])
+ return parent_map[p]
+
+ def union(p1, p2):
+ root1 = find(p1)
+ root2 = find(p2)
+ if root1 != root2:
+ parent_map[root2] = root1
+
+ for i in range(num_videos):
+ for j in range(i + 1, num_videos):
+ path_a = paths[i]
+ path_b = paths[j]
+
+ hashes_a = video_hashes[path_a]
+ hashes_b = video_hashes[path_b]
+
+ min_len = min(len(hashes_a), len(hashes_b))
+ if min_len == 0:
+ continue
+
+ distances = [hashes_a[k] - hashes_b[k] for k in range(min_len)]
+ avg_distance = sum(distances) / min_len
+
+ if avg_distance <= threshold:
+ union(path_a, path_b)
+
+ groups = {}
+ for p in paths:
+ root = find(p)
+ if root not in groups:
+ groups[root] = []
+ groups[root].append(p)
+
+ return [g for g in groups.values() if len(g) > 1]
+
+def display_table(properties_list):
+ headers = ["Relative Path", "Resolution", "Duration", "FPS", "File Size"]
+ col_widths = [max(len(str(p.get(h, ''))) for p in properties_list) for h in ['rel_path', 'resolution', 'duration', 'fps', 'size']]
+ col_widths = [max(w, len(h)) for w, h in zip(col_widths, headers)]
+
+ header_str = " | ".join(f"{h:<{w}}" for h, w in zip(headers, col_widths))
+ print("-" * len(header_str))
+ print(header_str)
+ print("-" * len(header_str))
+
+ for props in properties_list:
+ row_str = " | ".join(f"{str(props.get(k, '')):<{w}}" for k, w in zip(['rel_path', 'resolution', 'duration', 'fps', 'size'], col_widths))
+ print(row_str)
+ print("-" * len(header_str))
+
+if __name__ == '__main__':
+ target_folder = r"E:\Pictures\他人作品\ホロ"
+ root_path = Path(target_folder)
+
+ video_extensions = {'.mp4', '.avi', '.mkv', '.mov', '.flv', '.wmv'}
+ video_paths = []
+
+ for p in root_path.rglob('*'):
+ if p.suffix.lower() in video_extensions:
+ video_paths.append(p)
+
+ video_hashes = load_or_calculate_hashes(root_path, video_paths, sample_frames=10)
+ groups = group_similar_videos(video_hashes, threshold=12)
+
+ if not groups:
+ print("類似動画は検出されませんでした。")
+ else:
+ for idx, group in enumerate(groups, 1):
+ print(f"\n[Match Group {idx}/{len(groups)}] 一致数: {len(group)}")
+
+ props_list = [get_video_properties(p, root_path) for p in group]
+ display_table(props_list)
+
+ open_in_explorer(group)
+
+ if idx < len(groups):
+ input("\n次のグループを表示するには Enter キーを押してください...")
+ else:
+ print("\nすべてのグループの処理が完了しました。")
diff --git a/upscale_image.py b/upscale_image.py
@@ -0,0 +1,40 @@
+
+# import cv2
+
+# def upscale_image(image_path, output_path, model_path):
+# sr = cv2.dnn_superres.DnnSuperResImpl_create()
+# sr.readModel(model_path)
+# sr.setModel("espcn", 4)
+
+# image = cv2.imread(image_path)
+# result = sr.upsample(image)
+# cv2.imwrite(output_path, result)
+
+# if __name__ == "__main__":
+# upscale_image(r"C:\Users\ryout\Downloads\DSCN1883.JPG", "output_espcn.jpg", r"C:\Users\ryout\Downloads\ESPCN_x4.pb")
+#
+
+
+import cv2
+from realesrgan import RealESRGANer
+from basicsr.archs.rrdbnet_arch import RRDBNet
+
+def super_resolve_ai(image_path, output_path):
+ model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=4)
+
+ upscaler = RealESRGANer(
+ scale=4,
+ model_path='https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth',
+ model=model,
+ tile=0,
+ tile_pad=10,
+ pre_pad=0,
+ half=False
+ )
+
+ img = cv2.imread(image_path, cv2.IMREAD_COLOR)
+ output, _ = upscaler.enhance(img, outscale=4)
+ cv2.imwrite(output_path, output)
+
+if __name__ == "__main__":
+ super_resolve_ai(r"C:\Users\ryout\Downloads\DSCN1883.JPG", "output_realesrgan.jpg")
diff --git a/wav2flac.nu b/wav2flac.nu
@@ -0,0 +1,24 @@
+def main [
+ path: path # 変換対象のディレクトリパス
+] {
+ if not ($path | path exists) {
+ print $"Error: Path '($path)' does not exist."
+ return
+ }
+
+ let targets = (ls ([$path, "*.wav"] | path join))
+
+ if ($targets | is-empty) {
+ print "No .wav files found."
+ return
+ }
+
+ $targets | par-each { |it|
+ let output = ($it.name | str replace --regex '(?i)\.wav$' '.flac')
+ print $"Converting: ($it.name) -> ($output)"
+
+ ffmpeg -i $it.name -compression_level 8 -loglevel error $output
+ }
+
+ print "Done."
+}