ASMRç°å¢é³åç»ãç¡æããŒã«ã«ã§éç£ãã ïŒå š2åã®ç¬¬1åïŒïŒåç·š â 鿢ç»ã«ãæ¬ç©ã®éšããéãããç©çã·ãã¥ãšã·ãŒã ã¬ã¹ã«ãŒã
倧åŠ2幎ã®ãšãæ10äžã ã£ãåå ¥ããæãæã¡ã§60äžã«ãªããäŒç€Ÿéœåã§äžç¬ãŒãã«èœã¡ããããããå幎ã§Claude Codeãäžå¿ãšããèªåŸç°å¢ãçµã¿äžããä»ã¯æå120äžããã®ã¹ã±ãŒã«ãæ¯ããŠããæ±ã®ã²ãšã€ããäœãããªããŠãæ¯æ¥YouTubeã«ASMRåç»ãæçš¿ãããŠããä»çµã¿ã§ãã
ãªããã®ä»çµã¿ãå¹ãã®ã
ASMRãã£ã³ãã«ã«ã¯ä»ã®ãžã£ã³ã«ãšæ±ºå®çã«éãç¹æ§ããããŸãã1èŠèŽãããã®åçæéãç°åžžã«é·ãã®ã§ããç ããªãå€ã«ããã£ã±ãªãã«ãããå匷äžã«ãã£ãšæµãã30ååç»ãæåŸãŸã§èŠãŠããããã°ãåºååçã®èšç®åŒãæ ¹æ¬ããå€ãããŸããåãç»é²è æ°ã§ããçå°ºãšã³ã¿ã¡ç³»ãã£ã³ãã«ã®2ã4åã®åçã«ãªãããšã¯çãããããŸããã
åé¡ã¯äŸçµŠã³ã¹ãã§ãããASMRã§ææãåºããŠãããã£ã³ãã«ã®ã»ãšãã©ã¯æ¯æ¥æçš¿ããŠããŸãã30ååç»ãæ¯æ¥æäœæ¥ã§ç·šéããã®ã¯çŸå®çã§ã¯ãªããAIåç»çæïŒSoraãRunway Gen-3ãªã©ïŒã§ãªã¢ã«ãªéšãæãããšãããš1æ¬ãããæ°çŸåãæ°ååããããŸããæåã®æ±ã«ããã«ã¯æ¯æ¥30æ¬ä»¥äžã¹ããã¯ããŠããããã®ã§ããã®éé¡ã¯è«å€ã§ããã
転æç¹ã«ãªã£ãæ°ã¥ãã¯åçŽã§ãã**ASMRåç»ã«æ±ããããŠããã®ã¯ããã€ãããã¯ãªã«ããå²ããã§ã¯ãªããçªã®éšç²ããã£ããæµããéããã**ã§ããã«ã¡ã©ã¯åããªããã·ãŒã³ãåãæ¿ãããªããå¿ èŠãªã®ã¯ã鿢ãã宀å 颚æ¯ã®äžã§éšç²ã ããããããåããŠããããã®è³ªæã§ãã
ããã¯éæ¢ç»ã§äœããŸãã
RealVisXLïŒããŒã«ã«ã®ComfyUIã«ä¹ããç»åçæã¢ãã«ïŒãåºãã1æã®éæ¢ç»ã«ãPythonã§æžããéšç²ã®ç©çã·ãã¥ã¬ãŒã·ã§ã³ããå€äœããããçæããffmpegã®displaceãã£ã«ã¿ãŒã§åæãããåç»çæã¢ãã«ã¯äœ¿ããªããGPU代ãŒããçæã«ãããã®ã¯CPUæéã ãã§ãã
ããã²ãšã€ã®æ žå¿ãã·ãŒã ã¬ã¹ã«ãŒãã§ãã~/dev/asmr-factory/daily.shã®å®è£
ã§ã¯LOOPSEC=16ãããªãã¡16ç§ã®ã«ãŒãåç»ãmake_full.shã§30åã«ã¿ã€ã«ããŠããŸãã16ç§ã§1åšãããšããããšã¯ã30ååç»ã®äžã«ç¶ãç®ãçŽ112åçºçãããšããããšã§ãããã®ç¶ãç®ãç®ç«ãŠã°ã³ã¡ã³ãæ¬ã¯ã¯ã¬ãŒã ã§åãŸããã ããå€äœãããã¯å
é ãã¬ãŒã ãšæ«å°Ÿãã¬ãŒã ã®å€äœéãå®å
šã«äžèŽããããåšæèšèšãããŠããŸãããããåçŽãªãã©ã³ãã éšç²ã¢ãã¡ããšæ ¹æ¬çã«éããšããã§ãå
šé£èŒãéããŠæãéèŠãªæè¡çèã§ãã
ãæè²å æºïŒçªãæ§å³ã«çµã£ãçç±
READMEã«ãããŒã: 7çš®ã»å šãŠæè²å æº+çªå®€å æ§å³ã§çµ±äžããšæžããŠãããŸãããªãçµ±äžããã®ãã
èªåãã¹ã¯ïŒlib/auto_masks.pyïŒãçªé åãšçé åãèªåæ€åºããŠãéšãšãã§ã¯ããšçãããã®é©çšç¯å²ã決ããŸãããã®èªåæ€åºã¯æ§å³ãå®å®ããŠããªããšç²ŸåºŠãèœã¡ãŸãããã«ãã§ã®çªéããæçã®ããæžæããéšã®èŠããå宀ããªã©ãããŒãã¯7çš®ãããŸãããçªã¯å¿
ãç»é¢ã«æ ã£ãŠããŠæè²å
æºããããšããå¶çŽã«çµãããšã§ããã¹ã¯ç²ŸåºŠãå®çšã¬ãã«ã«å®å®ããŸãããããŒããçµãããšãçŸçãªå€æã§ã¯ãªããèªååãæç«ãããèšèšå€æã ã£ãããã§ãã
ç¡äººéçšã®å®çžŸ
~/dev/asmr-factory/ã®launchdèšå®ïŒcom.lily.asmr-dailyïŒã¯æ¯æ7:00ãš14:00ã«daily.shãèµ·åããŸããæ7æåã®çæãå®èµ°ããã°14æã¯ã¹ããããããŸãïŒåªçæ§èšèšïŒãç§ãããããšã¯ç¿æã«YouTube StudioãéããŠå
¬éãã¿ã³ãæŒãã ãããã以å€ã¯å®å
šã«èªåã§ãã
å šäœã®æµã
ãã€ãã©ã€ã³å šäœã®æ§æã§ãã
ComfyUI (RealVisXL @ 127.0.0.1:8188)
â still.png â 1024Ã576px
âŒ
lib/auto_masks.py
â win_mask.png (çªé åãã¹ã¯)
â fire_mask.png (çé åãã¹ã¯ã»ããŒãã«ããã¹ããã)
âŒ
lib/gen_rain_glass_map.py â â
ä»åã®äž»åœ¹
â maps/daily_16s/map_0001.png
â maps/daily_16s/map_0002.png
â ⊠(24fps à 16s = 384æ)
â â» ååçæåŸã¯ãã£ãã·ã¥äœ¿ãåã
âŒ
lib/render_loop.sh â â
ä»åã®äž»åœ¹
â loop.mp4 (16ç§ã·ãŒã ã¬ã¹ã«ãŒã)
âŒ
lib/freesound_fetch.py + lib/mix_audio.sh
â bed.wav (CC0鳿º / loudnorm -20LUFS / 2åå°º)
âŒ
lib/make_full.sh
â video.mp4 (30åã»ã«ãŒããã¿ã€ã«)
âŒ
lib/make_thumb.py + lib/make_meta.py
â thumbnail.png (1280Ã720) / youtube.md
âŒ
lib/youtube_upload.py
YouTubeãéå
¬éãã¢ãã (å
¬éã¯äººéã確èªããŠæŒã)
åã¹ããããdaily.shã®ã³ãŒããšåãããŠèªãã§ãããŸãã
ã¹ããã1: ComfyUIç»åçæ
SEEDBASE=$(( $(date -j -f "%Y-%m-%d" "$DATE" +%s 2>/dev/null \
|| date -d "$DATE" +%s) % 100000 ))
ok=0
for att in 0 1 2; do
SEED=$(( SEEDBASE + att*777 ))
log "comfy_gen attempt $att seed=$SEED"
if python3 "$LIB/comfy_gen.py" \
--prompt "$PROMPT" --seed "$SEED" --out "$STILL" \
--timeout 300 >>"$LOG" 2>&1; then
ok=1; break
fi
sleep $(( (att+1)*10 ))
done
[ "$ok" -eq 1 ] || die "image generation failed after retries"
ïŒdaily.sh L101â111ããïŒ
ã·ãŒãå€ã¯æ¥ä»ã®UNIXã¿ã€ã ã¹ã¿ã³ãã100000ã§å²ã£ãäœããããŒã¹ã«ããŠããŸãããªãã©ã€ã®ãã³ã«+777ãã€ãããããšã§ãåäžæ¥ä»ã§ãå詊è¡ããšã«å¥ã·ãŒãã«ãªããŸããComfyUIèªäœãèœã¡ãŠããå Žåã¯daily.shã®L50â60ã«ããensure_comfyui()颿°ãèªåèµ·åã詊ã¿ãŸãã150åïŒïŒçŽ10åïŒè©Šè¡ããŠç«ã¡äžãããªããã°dieã§å®å
šã«äžæããŸãã
ã¹ããã2ã3: ãã¹ã¯æ€åºãšå€äœãããçæ
# ---- 3. éšããã(ãµã€ãºåºå®ãªã®ã§ãã£ãã·ã¥æµçš) ----
LOOPSEC=16
MAPDIR="$ROOT/maps/daily_${LOOPSEC}s"
if [ ! -f "$MAPDIR/map_0001.png" ]; then
log "generating rain map (cache)"
mkdir -p "$MAPDIR"
python3 "$LIB/gen_rain_glass_map.py" \
--w 1024 --h 576 --fps 24 --seconds "$LOOPSEC" \
--drops 46 --out "$MAPDIR" --seed 7 >>"$LOG" 2>&1 \
|| die "rain map gen failed"
fi
ïŒdaily.sh L121â127ããïŒ
ãããä»åã®é£èŒã§æãéèŠãªç®æã§ããif [ ! -f "$MAPDIR/map_0001.png" ] ãšããæ¡ä»¶åå²ãèŠãŠãã ãããå€äœãããã®çæã¯ç涯ã§1åã ãèµ°ãã2åç®ä»¥éã¯maps/daily_16s/ãã£ã¬ã¯ããªå
šäœã䜿ãåããŸãã
ãã©ã¡ãŒã¿ã®æå³ïŒ
--fps 24 --seconds 16â åèš384æã®å€äœããããçæïŒ24Ã16ïŒ384ïŒ--drops 46â ç»é¢å ã«åæååšããéšç²ã®æ°ã46ã¯å¯åºŠãšåŠçé床ã®ãã©ã³ã¹ãåã£ãå€--seed 7â æ¯ååãããããåçŸããïŒæ±ºå®è«çã»åçŸæ§ä¿èšŒïŒ--w 1024 --h 576â ComfyUIã®åºåè§£ååºŠãšæããïŒæããªããšdisplaceããããïŒ
gen_rain_glass_map.pyãäœãããŠãããã®è©³çްã¯åŸç·šã§æãäžããŸãããäžèšã§èšããšãçªã¬ã©ã¹è¡šé¢ã®ç©çã¢ãã«ã䜿ã£ãŠã384æã®Grayscale PNGïŒå
é ãšæ«å°Ÿã®å€äœéãäžèŽããããåšæçã«èšèšãããå€äœãããïŒãçæãããã¹ã¯ãªããã§ãã
ã¹ããã4: render_loop.sh ã§ffmpegåæ
# ---- 4. ã«ãŒãåç»(ã¢ãŒã·ã§ã³) ----
LOOP="$WORK/loop.mp4"
WIN_MASK_ENV=""; FIRE_MASK_ENV=""
[ "$HAS_RAIN" = "True" ] && WIN_MASK_ENV="$WORK/win_mask.png"
[ "$HAS_FIRE" = "True" ] && FIRE_MASK_ENV="$WORK/fire_mask.png"
WIN_MASK="$WIN_MASK_ENV" FIRE_MASK="$FIRE_MASK_ENV" RAIN_OP=0.8 \
bash "$LIB/render_loop.sh" "$STILL" "$MAPDIR" "$LOOPSEC" "$LOOP" \
>>"$LOG" 2>&1 || die "render_loop failed"
ïŒdaily.sh L129â135ããïŒ
RAIN_OP=0.8ã¯éšãšãã§ã¯ãã®äžéæåºŠã§ãã1.0ã«ãããšå€äœéã匷ããªããããŠã¬ã©ã¹ãæªãã§èŠãã0.6ã ãšéšãèããããå®éšã®çµæ0.8ãæãèªç¶ã«èŠããŸãããrender_loop.shã¯ãããç°å¢å€æ°ãšããŠåãåããffmpegã®displaceãã£ã«ã¿ãŒã®ãã©ã¡ãŒã¿ã«å±éããŸãã
HAS_RAINãšHAS_FIREã¯themes.jsonã®ããŒãå®çŸ©ããæ¥ãŠããŸãããæçã®æžæãããŒããªãHAS_FIRE=Trueãã€HAS_RAIN=Falseããéšã®ã«ãã§ããªãHAS_RAIN=Trueãã€HAS_FIRE=Falseããéšã®æçããªãäž¡æ¹Trueã§ãããã¹ã¯ãã¡ã€ã«ãååšãããã©ããã§åŠçãåå²ããäœèšãªãã£ã«ã¿ãŒã¯åãŸãªãèšèšã«ãªã£ãŠããŸãã
ã¹ããã5ã6: CC0鳿ºãš30åå
for lic in cc0 any; do
if timeout 90 python3 "$LIB/freesound_fetch.py" \
--query "$Q" --minlen 25 --license "$lic" \
--out "$SRC" >"$WORK/fs_${i}.json" 2>>"$LOG"; then
fetched=1; break
fi
done
ïŒdaily.sh L146â152ããïŒ
鳿ºã¯Freesound APIããCC0ã©ã€ã»ã³ã¹ãåªå
ããŠååŸããŸãïŒcc0 â anyã®é ã§ãã©ãŒã«ããã¯ïŒãtimeout 90ã¯Freesoundã®ãã¬ãã¥ãŒDLã«ã¿ã€ã ã¢ãŠãããªãããæåã§ãããŠããŸããååŸããè€æ°ã®é³æºã¯mix_audio.shã§loudnorm -20LUFSã«çµ±äžããã¯ã¹ãããŸããLUFSãçµ±äžããªããšåç»ããŸããã§é³éãæŽããã®ã§ãããã¯çããªãå·¥çšã§ãã
make_full.shã¯ã«ãŒãåç»ãšé³æºãåãåã£ãŠ30åïŒããã©ã«ãMIN=30ïŒã«ã¿ã€ã«ããŸããã«ãŒãåç»16ç§ÃçŽ112.5åïŒ30åãffmpegã®-stream_loopãªãã·ã§ã³ã§åç»ãç¹°ãè¿ãã鳿ºã¯-shortestã§ã¯ãªãæç€ºçã«MINåã§åãåºããŸãã
ã¹ããã7ã9: ãµã ãã»ã¡ã¿ã»æ€èšŒ
# ---- 9. æ€èšŒ ----
DUR=$(ffprobe -v error -show_entries format=duration \
-of csv=p=0 "$VIDEO" 2>/dev/null | cut -d. -f1)
WANT=$((MIN*60))
[ -n "$DUR" ] && [ "$DUR" -ge $((WANT-3)) ] && \
[ "$DUR" -le $((WANT+3)) ] || die "duration check failed ($DUR != $WANT)"
STREAMS=$(ffprobe -v error -show_entries stream=codec_type \
-of csv=p=0 "$VIDEO" 2>/dev/null | sort | tr '\n' ',')
echo "$STREAMS" | grep -q "audio" && echo "$STREAMS" | grep -q "video" \
|| die "missing stream ($STREAMS)"
TW=$(python3 -c \
"from PIL import Image;print('x'.join(map(str,Image.open('$THUMB').size)))")
[ "$TW" = "1280x720" ] || die "thumb size $TW != 1280x720"
[ -s "$META" ] || die "meta empty"
ïŒdaily.sh L179â186ããïŒ
åºååã«4ç¹ã®æ€èšŒããããŸããâ åç»å°ºã30å±3ç§ä»¥å
ãâ¡æ åã¹ããªãŒã ãšé³å£°ã¹ããªãŒã ãäž¡æ¹ååšãããâ¢ãµã ãã1280x720ãâ£ã¡ã¿ãã¡ã€ã«ã空ã§ãªããã©ãã1ã€ã§ã倱æããã°dieã§ããã»ã¹ãèœã¡ãåŸè¿°ã®atomic moveãå®è¡ãããªãããDesktopã«ã¯äœãå±ããŸããããå£ããåç»ãYouTubeã«äžããããšããææªã±ãŒã¹ãããã§é²ãã§ããŸãã
ã¹ããã10: atomic stockãšåªçæ§
# ---- 10. atomic stock ----
STAGE="$WORK/_deliver"; mkdir -p "$STAGE"
cp "$VIDEO" "$STAGE/video.mp4"
cp "$THUMB" "$STAGE/thumbnail.png"
cp "$META" "$STAGE/youtube.md"
cp "$STILL" "$STAGE/scene.png"
mkdir -p "$DEST"
rm -rf "$FINAL"
mv "$STAGE" "$FINAL"
ïŒdaily.sh L189â197ããïŒ
äœæ¥ãã£ã¬ã¯ããªå
ã®_deliver/ã«ææç©ãéããŠããmvïŒatomicïŒã§Final destã«ç§»åããŸããã³ããŒéäžã§ããã»ã¹ãæ»ãã§ãå端ãªãã£ã¬ã¯ããªãDesktopã«æ®ããŸããã
åªçæ§ã¯L86â88ã®å€å®ã§æ
ä¿ãããŠããŸãã
EXIST=$(find "$DEST" -maxdepth 1 -type d \
-name "${DATE}_*" 2>/dev/null | head -1)
if [ -n "$EXIST" ]; then
log "already stocked for $DATE ($EXIST); skip (idempotent)"
exit 0
fi
åœæ¥åã1æ¬ã§ãååšããã°ããŒããéã£ãŠãå³exitããŸããlaunchdã7:00ãš14:00ã®2åèµ·åããŠãäºéçæã¯èµ·ããŸããã
ã¹ããã12: YouTubeãžã®éå ¬éã¢ããããŒã
media = MediaFileUpload(
spec["video"],
chunksize=8 * 1024 * 1024, # 8MBãã£ã³ã¯
resumable=True,
mimetype="video/mp4"
)
req = yt.videos().insert(
part="snippet,status", body=body, media_body=media
)
resp = None
while resp is None:
status, resp = req.next_chunk()
if status:
print(f" upload {int(status.progress()*100)}%",
file=sys.stderr)
ïŒlib/youtube_upload.py L73â80ããïŒ
resumable uploadã䜿ã£ãŠããã®ã§ãéäžã§åç·ãåããŠãåéã§ããŸããchunksize=8 * 1024 * 1024ïŒ8MBåäœïŒã¯30ååç»ïŒçŽ800MBã1GBïŒãå®å®ããŠéãããã®èšå®ã§ããã¢ããããŒãæååŸã«YouTube Studio URLããã°ã«åãïŒL91: https://studio.youtube.com/video/{vid}/editïŒãcoverage.csvã®ã¬ã³ãŒããOK+uploadedã«æŽæ°ããŸãã
éèŠãªã®ã¯ãã¢ããããŒãã倱æããŠãstockïŒ~/Desktop/ASMR/ã®ææç©ïŒã¯äžåæ¶ããªãããšã§ãïŒdaily.sh L206â226ïŒãããŒã¯ã³ãåããŠãããããããèœã¡ãŠãããã©ããªçç±ã§ãããŒã«ã«ã®video.mp4ã¯æ®ããŸããåŸããpython3 lib/youtube_upload.py --upload upload.jsonãæåå®è¡ããã°æžã¿ãŸãã
次åã¯lib/gen_rain_glass_map.pyã®ç©çã·ãã¥ã¬ãŒã·ã§ã³æ¬äœïŒç²ã®çæã»éåã»çªã¬ã©ã¹è¡šé¢åŒµåã¢ãã«ã»384æãåšæçã«çµã¶æ°åŠçèšèšïŒãšãlib/render_loop.shã®ffmpeg displaceãã£ã«ã¿ãŒæ§æã詳ããæãäžããŸãã
å®è£ ã®è©³çް
å€äœãããã®ãRGB3å±€æ§é ãããªãå¿ èŠã
lib/gen_rain_glass_map.py ã®åé ã³ã¡ã³ããèªããšãåºåPNGã®æ§é ãææåãããŠããŸãã
R = x-displacement (128 = neutral, refraction toward droplet center)
G = y-displacement (128 = neutral)
B = specular highlight (0 = none, bright = glint on the droplet)
äžè¬çãªå€äœãããã¯Grayscale1æã§ãæããã»ã©æŒãåºãã衚çŸãããŸãããããã§ã¯æšªæ¹åãšçžŠæ¹åãåæã«å¶åŸ¡ã§ããŸããããã®ãã€ãã©ã€ã³ã§ã¯Rãã£ã³ãã«ãšGãã£ã³ãã«ãç¬ç«ããå€äœè»žã«å²ãåœãŠãBãã£ã³ãã«ã«å ã®åå°ïŒã¹ããã¥ã©ãŒã°ãªã³ãïŒã忢±ããããšã§ã1æã®PNGã«3ã€ã®ç©çéãè©°ã蟌ãã§ããŸãã
render_loop.sh ã§ãããå±éããåŠçãããã§ãã
[1:v]setsar=1,split=3[m1][m2][m3];
[m1]extractplanes=r[xm];
[m2]extractplanes=g[ym];
[m3]extractplanes=b,format=gbrp[spec];
[todisp][xm][ym]displace=edge=smear,format=gbrp[disp];
[disp][spec]blend=all_mode=screen:all_opacity=0.85,format=gbrp[raineff];
ïŒlib/render_loop.sh L36â41ããïŒ
split=3 ã§åããã¬ãŒã ã3系統ã«åå²ããextractplanes=r/g/b ã§ããããã®ãã£ã³ãã«ãã°ã¬ãŒã¹ã±ãŒã«ãšããŠåãåºããŸããRïŒxmïŒãšGïŒymïŒãffmpegã® displace ãã£ã«ã¿ãŒã®Xã»Yå€äœæºã«ãªããBïŒspecïŒã¯ blend=all_mode=screen ã§äžããå ç®åæããŠå
ã®çœããã€ã©ã€ãã«ãªããŸãã
format=gbrp ã®æå®ãéæã«åºãŠããããšã«æ°ã¥ããã§ãããããffmpegã® displace ãã£ã«ã¿ãŒã¯YUVç³»ã®è²ç©ºéãå«ããŸããGBRPãšãããGãã£ã³ãã«ã»Bãã£ã³ãã«ã»Rãã£ã³ãã«ããã¬ãŒããŒé
眮ããéå§çž®ãã©ãŒãããããèŠæ±ããããã§ããããçç¥ãããšå®è¡æã« [Parsed_displace] incompatible pixel format ãåºãŠæ¢ãŸããŸãïŒåŸè¿°ïŒã
ã·ãŒã ã¬ã¹ã«ãŒããæç«ãããæ°åŠ
èŠãç®ã®ãªããããããéèŠãªã®ãã16ç§ã§å³å¯ã«äžåšããŠç¶ãç®ããŒãã«ãªãããšãã§ãããããæç«ãããŠããæ žå¿ã gen_rain_glass_map.py L38â44ã®é床èšèšã§ãã
for _ in range(args.drops):
n = int(rng.integers(1, 3)) # full wraps over T -> loop
r = float(rng.uniform(3, 9))
drops.append(dict(
...
speed=n * span / T,
...
))
ïŒlib/gen_rain_glass_map.py L37â44ããïŒ
span = H + 2 * args.marginïŒ576 + 80 = 656ãã¯ã»ã«ïŒãç»é¢çžŠå¹
ã«äžäžããŒãžã³ãå ããã1åšåã®ç§»åè·é¢ãã§ããn ã¯1ãŸãã¯2ã®æŽæ°ãspeed = n * span / T ãªã®ã§ã16ç§åŸã«åéšç²ãç§»åããè·é¢ã¯ speed * T = n * span ã«ãªããŸãã
ãã¬ãŒã ããšã®äœçœ®èšç®ãèŠãŠãã ããã
cy = (d["y0"] + d["speed"] * t) % span - args.margin
ïŒlib/gen_rain_glass_map.py L71ããïŒ
t = T ã®ãšã d["speed"] * T = n * span ãªã®ã§ãã¢ãžã¥ãæŒç®ã®çµæã¯ d["y0"] % span ãšçãããªããŸããã€ãŸã t=0 ãš t=T ã®äœçœ®ãå®å
šã«äžèŽããããããã·ãŒã ã¬ã¹ã«ãŒãã®æ°åŠçæ ¹æ ã§ãã
ãé床ãã©ã³ãã ã«ããã°éšç²ããã©ãã©ã«åããŠãªã¢ã«ã«ãªãã®ã§ã¯ïŒããšæåã«æããããããŸãããç§ãããæã£ãŠè©ŠããŸãããçµæã¯æšæºãããã®ã§ãããç¶ãç®ã§éšç²ããã¬ããŒãããã詳ããã¯åŸã®ãè©°ãŸã£ã話ãã§æžããŸãã
暪æºãïŒwobbleïŒãåšæå¶çŽãæºãã
çªã¬ã©ã¹ãäŒãéšç²ã¯çŽç·ã§ã¯èœã¡ãŸããã衚é¢åŒµåã®åãã§å·Šå³ã«èè¡ããŸãããããåçŸããã®ã wobamp ãš wobn ãã©ã¡ãŒã¿ã§ãã
cx = d["x0"] + d["wobamp"] * math.sin(
2 * math.pi * d["wobn"] * t / T + d["wobph"]
)
ïŒlib/gen_rain_glass_map.py L72ããïŒ
t = 0 ã®ãšãäœçžã¯ d["wobph"]ãt = T ã®ãšãäœçžã¯ 2 * pi * d["wobn"] * 1 + d["wobph"] ã§ããwobn ã¯1ãŸãã¯2ã®æŽæ°ãªã®ã§ã2 * pi * wobn 㯠2Ï ãŸã㯠4Ïãsin颿°ã®åšæã¯ 2Ï ãªã®ã§ãt=0 ãš t=T ã®X座æšãå¿
ãäžèŽããŸããåæäœçž wobph 㯠[0, 2Ï) ã§ã©ã³ãã ã«ã°ãã€ãããŠç²ããšã«ç°ãªãèè¡ã«èŠããŠããããã§ãã
ããŒããŒãããããšãã¬ã€ã«ã§ãASMRæããäœã
éšç²ã46åãã¹ãŠåãå°ããªãµã€ãºã«ãããšãç»é¢ãã«ããããããŠèŠèŠçã«èœã¡çããªããªããŸããç ãã«åŒãèŸŒãæ åã«ã¯ã倧ãããã£ããèœã¡ã䞻圹ã®éšç²ããšãèæ¯ã«åŒµãä»ãã现ããæ°Žæ»Žãã®ã¡ãªããªãå¿ èŠã§ãã
n_hero = args.hero if args.hero >= 0 else max(3, args.drops // 8)
for _ in range(n_hero):
r = float(rng.uniform(14, 26)) # fat hero droplet
drops.append(dict(
...
r=r, speed=1 * span / T, # n=1: one slow descent per loop
strength=float(rng.uniform(1.3, 1.8)),
trail=float(rng.uniform(0.8, 1.0)), glint=1.0,
))
ïŒlib/gen_rain_glass_map.py L51â61ããïŒ
éåžžç²ã®ååŸã 3ã9px ã«å¯ŸããŠããŒããŒç²ã¯ 14ã26pxãstrength ã 0.7ã1.1 ã«å¯Ÿã㊠1.3ã1.8 ã§ãã¬ã³ãºå¹æã匷ãããããŸãã--drops 46 ã§éåžžç²ã46åãmax(3, 46 // 8) = 5 ã§ããŒããŒç²ã5åãåèš51ç²ãç»é¢ã«å
±åããŸãã
ããŒããŒç²ç¹æã®æ©èœã trailïŒå°Ÿè·¡ïŒã§ãã
if d["trail"] > 0:
tpad_x = max(0, int(cx - 2)); tpad_x1 = min(W, int(cx + 2))
ty0 = max(0, int(cy - r * 6)); ty1 = max(0, int(cy))
if tpad_x1 > tpad_x and ty1 > ty0:
tsx = xx[ty0:ty1, tpad_x:tpad_x1] - cx
tdist = np.abs(tsx)
tfall = np.clip(1.0 - tdist / 2.0, 0, 1)
vfade = np.clip((yy[ty0:ty1, tpad_x:tpad_x1] - (cy - r * 6)) / (r * 6), 0, 1)
dx[ty0:ty1, tpad_x:tpad_x1] += -(np.sign(tsx)) * tfall * vfade * (args.disp * 0.25) * d["trail"]
ïŒlib/gen_rain_glass_map.py L95â103ããïŒ
éšç²ã®çäžã«å¹
4pxïŒcx±2ïŒã»é«ã r*6 ã®çŽ°ãæ±ãèšå®ããããã«åŸ®åŒ±ãªæšªå€äœïŒdisp * 0.25ïŒããããŸããæ°Žãéã£ãè·¡ã§ã¬ã©ã¹è¡šé¢ãèãæ¿¡ããå
ã®å±æã埮åŠã«æ®ãç¶æ
ãæš¡å£ããŠããŸããvfade ã¯éšç²ã«è¿ãã»ã©å€äœéã倧ãããé ãããã«ã€ããŒãã«è¿ã¥ãã°ã©ããŒã·ã§ã³ã§ãã
ffmpegãã£ã«ã¿ãŒã°ã©ãã®åççµã¿ç«ãŠ
render_loop.sh ã§ç¹åŸŽçãªã®ããç°å¢å€æ° WIN_MASK ãš FIRE_MASK ã®æç¡ã§ãã£ã«ã¿ãŒã°ã©ãæååãåçã«çµã¿ç«ãŠãèšèšã§ãã
FC="[0:v]format=gbrp,setsar=1[still];"
CUR="still"
if [ -n "$WIN_IN" ]; then
FC+="[1:v]setsar=1,split=3[m1][m2][m3]; ..."
CUR="rained"
fi
if [ -n "$FIRE_IN" ]; then
FC+="[${CUR}]split=2[fb][ff]; ..."
CUR="lit"
fi
FC+="[${CUR}]geq=r='r(X,Y)*${GFLK}':... ,format=yuv420p[vout]"
ïŒlib/render_loop.sh L30â54ããïŒ
CUR 倿°ããã€ãã©ã€ã³ã®ãçŸåšã®åºåã©ãã«ãã远跡ããŸããéšãšãã§ã¯ããæãã° CUR ã still â rained ã«æŽæ°ãããæ¬¡ã®çããã㯠rained ãå
¥åãšããŠåãåããã©ã¡ããã¹ãããããã° still ã«ã°ããŒãã«ããªãã«ãŒã ãä¹ããŠçµãããŸãã
ã°ããŒãã«ããªãã«ãŒã®åŒãèŠãŠãã ããã
C1=$(awk "BEGIN{printf \"%d\", 3*${LOOPSEC}}") # = 48
C2=$(awk "BEGIN{printf \"%d\", 7*${LOOPSEC}}") # = 112
GFLK="(0.975+0.018*sin(2*PI*${C1}*T/${LOOPSEC})+0.010*sin(2*PI*${C2}*T/${LOOPSEC}))"
ïŒlib/render_loop.sh L18â20ããïŒ
T ã¯ffmpegã®ãã¬ãŒã æå»ã衚ãçµã¿èŸŒã¿å€æ°ã§ããC1 = 3 * 16 = 48ãC2 = 7 * 16 = 112 ãšããä¿æ°ã䜿ãããšã§ãåšæ³¢æ°3Hzãš7Hzã®æ£åŒŠæ³¢ã®åã§èŒåºŠãå€èª¿ããŸããt = 0 ãš t = LOOPSEC = 16 ã§ã©ã¡ãã®æ£åŒŠæ³¢ãå®å
šã«äžåšãããããããªãã«ãŒãç¶ãç®ãªãã§ç¹ãããŸããæ¯å¹
0.018 ãš 0.010 ã¯å®€å
é»çã®ããããããªææ»
ã衚ãå€ã§ãæèçã«ç®ã远ããªãã®ãªã®ãªã®åŒ·åºŠãå®éšã§æ±ºããŸããã
çãããã¯åãåšæ³¢æ°ã䜿ããªããæ¯å¹
ã倧ããããäœçž 1.7 ãããããŠå€èª¿ã«ææ©çãªãããäœã£ãŠããŸãã
FFLK="(0.78+0.15*sin(2*PI*${C1}*T/${LOOPSEC})+0.07*sin(2*PI*${C2}*T/${LOOPSEC}+1.7))"
ïŒlib/render_loop.sh L21ããïŒ
åºæºå€ 0.78 ã¯çã®åœ±é¿äžã«ããé åãéåžžèŒåºŠã®78%ãŸã§èœãšãããã®ä¿æ°ã§ããæçããŒãã§ã¯ç»é¢ã®äžéšãæºããçã®å
ã§ææã亀äºã«åãæ¿ãããæ¯ããããããªãåããçãŸããŸãã
ç§ãè©°ãŸã£ã話
è©°ãŸã1: format=gbrp ãçãããäœãæ ããªãã£ã
æåã«æžãã filter_complex ã¯ã·ã³ãã«ãªæ§æã§ããã
[0:v][1:v][2:v]displace=edge=smear[out]
å®è¡ãããšæ åã¯åºåãããŸããããç»é¢ãæç·è²ã®åè²ã«å¡ãã€ã¶ãããŠããŸããããã°ã«ã¯äœãåºãŸããã-loglevel info ã«äžããŠèª¿ã¹ããš incompatible pixel formats in filter chain ãæµããŠããŸããã
åå 㯠displace ãã£ã«ã¿ãŒãYUV420Pã®ãŸãŸåãåããªãããšã§ãã鿢ç»ã¯JPEGç±æ¥ã®YUVãå€äœãããã¯RGBã®PNGããã®2ç³»çµ±ãæ··ããŠdisplaceã«æž¡ããšffmpegãå
éšã§ãã©ãŒããã亀æžã詊ã¿ãŠå€±æããŸãã
çŽãæ¹ã¯å€äœããããèªã¿èŸŒãã çŽåŸã« format=gbrp ãããã鿢ç»åŽãåæ§ã«å€æããŠãã displace ã«æž¡ãããšã§ããrender_loop.sh L30ã® [0:v]format=gbrp,setsar=1[still] ãšãL36ã® [1:v]setsar=1 ã«ç¶ããŠR/G/Bå±éããæ§é ããã®çãã§ããformat=gbrp ããããªããšéæ¢ç»ã®è²ãç·ã«åãçŸè±¡ãèµ·ãããã€ç¡é³ã§å€±æããã®ã§åå ãéåžžã«èŠã€ãã«ããã
è©°ãŸã2: ã«ãŒãã®ç¶ãç®ã§éšç²ããã¬ããŒããã
æåã®å®è£
ã§ã¯ speed ãã©ã³ãã ãªæµ®åå°æ°ç¹æ°ã«ããŠããŸãããrng.uniform(20, 80) ãã¯ã»ã«/ç§ã®ããã«èšå®ããããã©ãã©ã«åãã°ãªã¢ã«ã ããããšãã倿ã§ãã
çæããåç»ã30åçã§ç¢ºèªãããšã16ç§ããšã«å šéšç²ãç¬éç§»åããŠããã®ãæç¢ºã«ããããŸãããèŠèŠçã«ã¯é«éã³ãèœã¡ã®ãããªãã¬ã¯ãããšããæçµ¶ããéããªé³æ¥œã®äžã«112åç¹°ãè¿ãããææªã®çµæã§ããã
åå ã¯æ¢ã«èª¬æããéãã§ãt=0 ãš t=T ã§éšç²ã®äœçœ®ãäžèŽããªãããšã§ããspeed = n * span / T ãšãããæŽæ°åå
šspanç§»åãã®å¶çŽãå
¥ããç¬éã«è§£æ±ºããŸããã
å¯äœçšãšããŠéšç²ã®é床ããªãšãŒã·ã§ã³ã n=1 ã n=2 ã®2çš®é¡ã«éå®ãããŸãããååŸã®ãã©ã€ãïŒ3ã26pxïŒãšæšªæºãã®æ¯å¹
å·®ã§ãªã¢ãªãã£ã¯ååè£ããŠããŸãã
è©°ãŸã3: maskedmerge ã§ãã¹ã¯ã®çœé»ãéã ã£ã
auto_masks.py ãåºåãããŠã£ã³ããŠãã¹ã¯ã¯ãçªã®éšåãçœããã以å€ãé»ãã®æ³å®ã§æžããŠããŸãããããã ffmpegã® maskedmerge ãã£ã«ã¿ãŒã®ä»æ§ã確èªãããšã第3å
¥åãçœãã»ã©ç¬¬2å
¥åïŒãšãã§ã¯ãåŽïŒãæ¡çšãããšããåäœãæ£ããæåã§ãã
æåã®ãã¹ãã§ã¯çªã¬ã©ã¹ã®å€ïŒå£ã»å€©äºïŒã«éšç²å€äœãä¹ããçªã®äžã ãããšãã§ã¯ããªãã«ãªã£ãŠããŸãããèŠãç®ã¯ãå£ãæªãã§çªã鿢ããšããçéã®çµæã§ãã
auto_masks.py ã®ãã¹ã¯çæããžãã¯ã確èªããŠãçªé åã255ïŒçœïŒã§å¡ããéçªã0ïŒé»ïŒã§å¡ã£ãŠããããšã確ãããŸãããåé¡ã¯ffmpegãžã®æž¡ãæ¹ã§ã¯ãªããmaskedmerge ã®åŒæ°é ã§ããã
[base][raineff][winmask]maskedmerge
maskedmerge ã®å
¥å㯠[ããŒã¹æ å][ãšãã§ã¯ãæ å][ãã¹ã¯] ã®é ã§ããç§ã¯æå [raineff][base][winmask] ã®é ã§æž¡ããŠãããããããŒã¹ãšãšãã§ã¯ããå
¥ãæ¿ãã£ãç¶æ
ã§ãã¹ã¯åæãããŠããŸãããåŒæ°é ãæ£ããçŽãã ãã§è§£æ±ºããŸãããffmpegã®ããã¥ã¡ã³ãã¯åŒæ°é ã®èª¬æãèããå
¬åŒãµã³ãã«ãèªãã§ãã£ãšæ°ã¥ããŸããã
è©°ãŸã4: Freesoundã®ããŠã³ããŒããç¡éã«æ¢ãŸã£ã
鳿ºååŸã®åæå®è£
ã« timeout ããªããlaunchdãã7:00ã«èµ·åãããžã§ããæŒéããŸã§æ¢ãŸã£ãŠããããšããããŸããåå ã¯Freesoundã®ãã¬ãã¥ãŒããŠã³ããŒãURLãã³ã³ãã³ããè¿ãå§ããããšãµãŒããŒåŽã§ã»ãã·ã§ã³ãç¶æãããŸãŸçµç«¯ãéã£ãŠããªãã±ãŒã¹ã§ãããrequests.get ã¯ããã©ã«ãã§ã¿ã€ã ã¢ãŠããèšå®ããªãã®ã§ãããŠã³ããŒããæ°žé ã«ç¶ããŸãã
daily.sh L148ã® timeout 90 ã¯ãããé²ãããã§ãã
if timeout 90 python3 "$LIB/freesound_fetch.py" \
--query "$Q" --minlen 25 --license "$lic" \
--out "$SRC" >"$WORK/fs_${i}.json" 2>>"$LOG"; then
ïŒdaily.sh L148ããïŒ
90ç§ã§ããã»ã¹ããšæ®ºããŸãã倱æãã鳿ºã¹ããã㯠log "WARN: sound fetch failed: $Q" ã§èšé²ããä»ã®ã¹ãããã1ã€ã§ãæåããŠããã°åŠçãç¶ç¶ããŸãïŒL161ã® [ "$idx" -ge 1 ] ãã§ãã¯ïŒãã鳿ºãŒãã®ç¡é³åç»ãYouTubeã«äžãããã ãã¯é¿ããããã®æäœä¿èšŒã§ã鳿ºã1çš®é¡ããåããªããŠãåºè·ãããšãã倿ã§ãã
è©°ãŸã5: å€äœããããæ¯æ¥åçæããŠCPUãè©°ãŸã£ã
åæèšèšã§ã¯éšãããããã®æ¥ã®éæ¢ç»å°çšã«åçæããŠããŸããããè§£å床ã¯åºå®ãªã®ã«æ¯åçæããå¿ èŠãããã®ãïŒããšæ°ã¥ããŸã§ã«2é±éããããŸããã
384æã®PNGãçæããã®ã«ç§ã®M1 MacBookïŒCPUã¢ãŒãïŒã§çŽ3åããããŸãããããæ¯æ¥ã®çæã³ã¹ãã«å ç®ãããå šäœã7ã8åããã£ãŠããŸãããããã«éšãããã«ã¯éæ¢ç»ã®å 容ã¯äžåé¢ä¿ãããŸããããµã€ãºïŒ1024Ã576ïŒãšå°ºïŒ16ç§ïŒãšã·ãŒãïŒ7ïŒãåãã§ããã°ãã©ã®ããŒãã§çæããŠãåºãŠãããããã¯åãã§ãã
daily.sh L123ã® if [ ! -f "$MAPDIR/map_0001.png" ] ã¯ååçæåŸã¯æ¡ä»¶ãåœã«ãªãã¹ããããããŸãã2æ¥ç®ä»¥éã®CPUè² è·ãã3åãæ¶ããŸããããã®å€æŽåŸãdaily.sh ã®å®è¡æéã¯å¹³å4åå°ã«ãªã£ãŠããŸãã
次å㯠lib/auto_masks.py ã®ã»ã°ã¡ã³ããŒã·ã§ã³å®è£
ãšãlib/make_full.sh ã§16ç§ã«ãŒãã30åã«ã¿ã€ã«ããéã®é³ãºã¬å¯ŸçããããŠå®éã®ãã£ã³ãã«åçæ°åãæãããŸãã
ã€ãŸãããã€ã³ãïŒè¿œå ç·šïŒ
åæ®µã§è©³è¿°ãã5ç¹ïŒformat=gbrpã»ã«ãŒãç¶ãç®ãã¬ããŒãã»maskedmergeåŒæ°é ã»Freesoundç¡éãã³ã°ã»å€äœãããæ¯æ¥åçæïŒã«å ããŠãå®éçšã§èžãã å°é·ããŸãšããŸããã³ãŒããèªãŸãã«åãããšé«ç¢ºçã§è©°ãŸãç®æã°ããã§ãã
-
launchdã®PATHã¯
/usr/bin:/bin:/usr/sbin:/sbinãããªããdaily.shL8ã§export LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8ãæåã«ãã£ãŠããã®ã¯ãlaunchdã®æå°ç°å¢ã§ã¯æ¥æ¬èªãã°ãæååãããããã§ããPATHãåæ§ã§ãHomebrewãnvmã®Pythonãå ¥ã£ã/opt/homebrew/binã/usr/local/binã¯launchdããèŠããŸãããplistã®EnvironmentVariablesã«<key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>ãæç€ºããªããšffmpeg: command not foundãåºãŠãã¹ãŠèœã¡ãŸãã -
ã¯ã©ãã·ã¥åŸã«ããã¯ãã£ã¬ã¯ããªãæ®ããšç¿æ¥ä»¥éãå šæ» ããã
daily.shL32â43ã®ããã¯æ©æ§ã¯ãããã¯ä¿æPIDãkill -0ã§æ»ãã§ããã°å¥ªåãããèšèšã§ãããmacOSã®åèµ·åãæããšPIDãå¥ããã»ã¹ã«åå©çšãããããšããããŸãããã®å Žåãæ»ãã ã¯ãã®ããã¯ããçããŠããããã»ã¹ãæã£ãŠããããšèª€å€å®ãããŠexit 0ãç¶ããŸãã鱿¬¡cronãæåã§rm -rf ~/dev/asmr-factory/.daily.lock.dãä»èŸŒãã§ãããšä¿éºã«ãªããŸãã -
macOSã®
date -jãšLinuxã®date -dã¯äºæããªããdaily.shL101ãš L66ã§date -j -f "%Y-%m-%d" "$DATE" +%s 2>/dev/null || date -d "$DATE" +%sãšããORæ§æã§äž¡å¯Ÿå¿ããŠããŸãããã®ãã¿ãŒã³ãç¥ããã«çæ¹ã®OSã§æžããã¹ã¯ãªãããããçæ¹ã«ç§»æ€ãããšãæ¥ä»å€æã§date: illegal option -- dãŸãã¯date: invalid option -- 'j'ãåºãŠã·ãŒãèšç®ãå£ããŸãã -
YouTube Data API v3ã¯1æ¥10,000ã¯ã©ãŒã¿ã
videos.insertã¯1å1,600æ¶è²»ãªã®ã§1æ¥6æ¬ãäžéã--forceã§åçæïŒåã¢ãããç¹°ãè¿ããããå¥ãããžã§ã¯ããšåäžAPIãããžã§ã¯ããå ±çšããŠãããšç¿æã«ã¯ã©ãŒã¿è¶ éïŒHTTP 403ïŒãè¿ããŸããyoutube_upload.pyã«ãªãã©ã€åŠçã¯ãªãããããã®ã±ãŒã¹ã§ã¯daily.shL217ã®timeout 1200 python3 lib/youtube_upload.py ...ã倱æããL221ã®elseãã©ã³ãããWARN: YouTube upload failed (stockã¯ä¿æ)ããåããŠã¢ãããã¹ãããããŸããstockïŒ~/Desktop/ASMR/ïŒã¯å€±ãããªãã®ã§åŸæ¥æåã¢ããã¯å¯èœã§ãããã¯ã©ãŒã¿ã¯ç¿æ¥ãŸã§å埩ããŸããã -
åå
--authãå¿ãããšæ¯æ¥ã®ã¢ããããŒããå šã¹ãããããæ°ã¥ãã«ãããdaily.shL207â208ã§[ -f "$HOME/.youtube/token.json" ]ã確èªããŠããããã¡ã€ã«ãååšããªããã°ãYouTubeæªèªèšŒâã¢ããçç¥ãã®WARNãã°ãåºããŠæ£åžžçµäºããŸããstockèªäœã¯äœãããã®ã§çæã¯æåããŠããããã«èŠããŸãããåç»ã¯ãã£ãšYouTubeã«äžãããŸãããååã ãã€ã³ã¿ã©ã¯ãã£ãã»ãã·ã§ã³ã§python3 lib/youtube_upload.py --authãå®è¡ããŠãã©ãŠã¶åæãå®äºãããŠãã ããã -
YouTubeã¿ã€ãã«ã¯100æåã§Pythonã®ã¹ã©ã€ã¹ã«ãããµã€ã¬ã³ãã«åãæšãŠãããã
youtube_upload.pyL63ã®spec["title"][:100]ã¯äŸå€ãæããŸãããæ¥æ¬èªã®é·ãã¿ã€ãã«ãmake_meta.pyã§çµãã§ããAPIè¶ãã«éãããã®ã¯å é 100æåã ãã§ããYouTube Studioã§ç¢ºèªããŠã¯ãããŠæ«å°Ÿãæ¶ããŠããããšã«æ°ã¥ããã¿ã€ãã«ã¯75æå以å ãç®å®ã«ããããmake_meta.pyåŽã§assert len(title) <= 80ãå ¥ãããšå®å¿ã§ããŸãã -
--minutesãå¢ãããšmix_audio.shã®2åBED鳿ºãäžè¶³ãããdaily.shL164ã®bash "$LIB/mix_audio.sh" "$BED" 120 ...ã¯120ç§ïŒ2åïŒã®BEDãçæããŸãã30åãžã®æ¡åŒµã¯make_full.shãã«ãŒãåŠçããŸãã--minutes 60ã«å€ãããšãmake_full.shã®å éšã«ãŒãå®è£ ã«ãã£ãŠã¯2åã®é³æºã60åã«ããŸãå»¶ã³ãªãå ŽåããããŸããåæ°ãå€ããéã¯mix_audio.shã®120ã$MIN*60ã«æããŠç¢ºèªããŠãã ããã -
sed -i ''ã¯macOSå°çšã§ãLinuxã§ã¯sed: 1: "..."ã®ãšã©ãŒã«ãªããdaily.shL220ã®sed -i '' "s|,OK$|,OK+uploaded|" "$ROOT/coverage.csv" 2>/dev/null || trueã¯macOSçsedåãã§ãã|| trueã§ãšã©ãŒãsilenceããŠãããããLinuxç°å¢ã«ç§»æ€ããŠãcoverage.csvãæŽæ°ãããªããŸãŸæ£åžžã«èŠããŸããLinuxç§»æ€æã¯sed -i "s|..."ã«æžãæããŠãã ããã -
æ€èšŒã¹ãããã
from PIL import ImageãåŒã¶ããPillowãå¿ é ãdaily.shL184ã®python3 -c "from PIL import Image;print(...)"ã§ãµã ããµã€ãºãæ€èšŒããŠããŸããPillowãå ¥ã£ãŠããªãç°å¢ã§ã¯ModuleNotFoundErrorã§dieããŸããlaunchdãåŒã³åºãPythonïŒéåžžHomebrewç³»ïŒã«pip3 install Pillowãæžãã§ããã確èªããŠãã ãããéçºäžã¯èªåã®venvã«å ¥ããŠããŠããlaunchdãå¥ã®PythonãåŒãã§ããŠè©°ãŸãã±ãŒã¹ãå®éã«ãããŸããã -
ComfyUIã®CPUã¢ãŒãã§
--timeout 300ïŒ5åïŒã¯çããããdaily.shL53ã§nohup .venv/bin/python main.py --port 8188 --cpuãšããŠèµ·åãããããM1 CPUã§1024Ã576ã®ç»åçæã«3ã8åããããŸããcomfy_gen.py --timeout 300ã®äžéã«éããŠãªãã©ã€ãèµ°ããatt=0,1,2ã®3åãã¹ãŠã§å€±æãããšdie "image generation failed after retries"ã§æ¢ãŸããŸããCPUå°çšãã·ã³ã§ã¯--timeout 600ã«å»¶ã°ãããlaunchdèµ·ååã«ComfyUIãç«ã¡äžããŠããããšã§å®å®ããŸãã -
--stillã§æ¢åç»åãåå©çšãããšã--forceãªãã ãšåªçãã§ãã¯ã«åŒŸããããdaily.shL86â89ã®åªçãã§ãã¯ã¯FORCE=0ã®ãšãåœæ¥åã®ææç©ãããã°å³exit 0ããŸãã--still scene.png --theme-id Xã§ãªããã¯ã¹ããããšããŠãåœæ¥åãååšããã°äœãããã«çµãããŸããåçæã»åããã¯ã¹ãæç€ºããããšãã¯--forceãå¿ ãä»ããŠãã ããã
ãã¹ããã©ã¯ãã£ã¹
å幎ã®ç¡äººéçšã§ããããå®ããšå£ããªãããšç¢ºèªã§ããååã§ãã
1. å€äœãããã¯åå1åã ãçæããŠãã£ãã·ã¥ãã
daily.sh L123ã® if [ ! -f "$MAPDIR/map_0001.png" ] ããããå®çŸããŠããŸãã384æã®PNGçæïŒCPUã§çŽ3åïŒãååéå®ã«ããçµæãæ¥æ¬¡å®è¡æéã7ã8åãã4åå°ã«èœã¡ãŸãããè§£å床ïŒ1024Ã576ïŒãšã«ãŒãç§æ°ïŒ16ç§ïŒãå€ãããªãéããããããåçæããçç±ã¯ãŒãã§ãã
2. éšç²ã®é床㯠n à span / TïŒnã¯æŽæ°ïŒã«åºå®ããŠã«ãŒããæ°åŠçã«ä¿èšŒãã
gen_rain_glass_map.py L38â44ã® speed = n * span / TïŒnã¯1ãŸãã¯2ïŒãã·ãŒã ã¬ã¹ã«ãŒãã®æ ¹æ ã§ããt=T ã§åç²ãç§»åããè·é¢ã¯ n * span ã®æŽæ°åã«ãªããã¢ãžã¥ãæŒç®ã®çµæã t=0 ãšå®å
šã«äžèŽããŸããæšªæºãã wobnïŒ1ãŸãã¯2ã®æŽæ°ïŒã䜿ã£ãæ£åŒŠæ³¢ãªã®ã§ t=T ã§äžåšããŸããé床ããªãšãŒã·ã§ã³ã2çš®ã«çµã代ããã«ååŸïŒ3ã26pxïŒãšæ¯å¹
å·®ã§å€æ§æ§ãè£ãããã®ãã¬ãŒããªããæéèŠèšèšã§ãã
3. ãã£ã«ã¿ãŒã°ã©ãã«ã¯å¿
ã format=gbrp ãå
é ã«çœ®ã
render_loop.sh L30ã® [0:v]format=gbrp,setsar=1[still] ã¯å¿
é ã§ããdisplace ãã£ã«ã¿ãŒã¯YUVç³»ãåãä»ããã倿ãªãã§æž¡ããšç»é¢ãæç·è²ã«ãªããç¡èšã§èœã¡ãŸããJPEGç±æ¥ã®YUV鿢ç»ãšPNGå€äœããããæ··åãããã¹ãŠã® filter_complex ã«ãã®ã«ãŒã«ãé©çšããŠãã ããã
4. ãšãã§ã¯ã匷床ã¯ç°å¢å€æ°ã§å¶åŸ¡ããŠããŒãã³ãŒããé¿ãã
RAIN_OP=0.8ïŒdaily.sh L134ïŒãGFLKãFFLKïŒrender_loop.sh L18â21ïŒã¯ãã¹ãŠç°å¢å€æ°ãŸãã¯åçèšç®å€ã§ãã匷床ã®èª¿æŽãæ°å€1ã€ãæžãæããã ãã§æžããããããŒãããšã«å¥ã¹ã¯ãªãããæã€å¿
èŠããããŸããã
5. 鳿ºååŸã¯ timeout 90 ïŒ cc0 â any ãã©ãŒã«ããã¯ã®2段æ§ãã«ãã
Freesoundã®ãã¬ãã¥ãŒé
ä¿¡ã¯çµç«¯ãéã£ãŠããªãããšããããã requests.get ã ãã§ã¯æ°žé ã«ãã³ã°ããŸãïŒdaily.sh L148ïŒã90ç§ã¿ã€ã ã¢ãŠãã»CC0åªå
ã»ã©ã€ã»ã³ã¹ãã©ãŒã«ããã¯ã®3å±€ã§ã鳿ºãŒãã®ç¡é³åç»ãã確å®ã«é²ãã§ããŸãã
6. åºååã«4ç¹æ€èšŒã die ã§èµ°ãããŠã²ãŒããèšãã
åç»å°º30å ±3ç§ã»æ åãšé³å£°ã¹ããªãŒã ã®äž¡ååšã»ãµã ã 1280x720ã»ã¡ã¿ãã¡ã€ã«é空ïŒdaily.sh L179â186ïŒããã®æ€èšŒã²ãŒããéã£ãææç©ã ãã ~/Desktop/ASMR/ ã«å±ãèšèšã§ãããå£ããåç»ãYouTubeã«äžãããææªã±ãŒã¹ãæ©æ¢°çã«é²ããŸãã
7. ææç©ã®ç§»å㯠_deliver/ ãžã®ã³ã㌠â mv ïŒatomicïŒã§è¡ã
daily.sh L189â197ã®ããã« _deliver/ ã«å®æåãéããŠãã rm -rf "$FINAL"; mv "$STAGE" "$FINAL" ã§ç§»åããŸããcp ã®éäžã§ããã»ã¹ãæ»ãã§ãäžéå端ãªãã£ã¬ã¯ããªãDesktopã«æ®ãããå€ãææç©ãšæ°ããææç©ãç¬éã§ãåæã«ååšããªãèšèšã§ãã
8. åªçæ§ã¯ãæ¥ä»ã§1æ¬ã§ãååšããã°skipãã§å®è£ ãã
daily.sh L87â88ã® find "$DEST" -maxdepth 1 -type d -name "${DATE}_*" ã«ããå€å®ã§ããããŒããå€ãã£ãŠã忥2æ¬ç®ã¯çæããŸãããlaunchdã7:00ãš14:00ã«2åèµ·åããŠãäºéçæã¯èµ·ããªãã--force ã§æç€ºçã«äžæžãã§ããèšèšã«ããŠããããšã§ãidempotencyãšæååçæã®äž¡æ¹ã1ã€ã®ãã©ã°ã§å¶åŸ¡ã§ããŸãã
9. ã¢ããããŒã倱æã§ãstockãæ¶ããªããçæãšã¢ããããŒããåãé¢ã
daily.sh L221ã® else ãã©ã³ãã¯WARNãã°ãåãã ãã§ die ããŸããã~/Desktop/ASMR/ ã® video.mp4 ãæ®ããåŸãã python3 lib/youtube_upload.py --upload upload.json ã§æåã¢ããã§ããŸãããããé害ã»ããŒã¯ã³åãã»ã¯ã©ãŒã¿è¶
éã®åœ±é¿ç¯å²ãã¢ããããŒãå·¥çšã ãã«å°ã蟌ããèšèšã§ãçæç©ã®æå€±ã¯èµ·ããŸããã
10. ã°ããŒãã«ããªãã«ãŒã¯3Hzãš7HzïŒäºãã«çŽ ïŒã®éãåããã«ãã
render_loop.sh L18â20ã® GFLK ã 3*LOOPSEC=48 ãš 7*LOOPSEC=112 ãšããä¿æ°ã䜿ãã®ã¯ãã©ã¡ããã«ãŒãç§æ°ã®æŽæ°ååšæãªã®ã§ç¶ãç®ã«åœ±é¿ããªãããã§ãïŒt=0 ãš t=T=16 ã§ã©ã¡ãã®æ£åŒŠæ³¢ãäžåšããïŒãæ¯å¹
ã¯å®€å
é»çã®åŸ®åŒ±ãªææ»
ïŒ0.018ãš0.010ïŒã«èšå®ããŠãããæèçã«ç®ã远ããªãã®ãªã®ãªã®åŒ·åºŠã§ããåäžåšæ³¢æ°ã§ã¯äººå·¥çãªã¡ãã€ãã«èŠããã©ã³ãã ãã€ãºã§ã¯ã«ãŒããå²ããŸãã
11. ããŒãããæè²å æºïŒçªãã«çµ±äžããŠèªåãã¹ã¯ç²ŸåºŠãå®å®ããã
auto_masks.py ã®çªã»çé 忀åºã¯æ§å³ãå®å®ããŠããªããšç²ŸåºŠãèœã¡ãŸããREADMEã«ãããŒã: 7çš®ã»å
šãŠæè²å
æº+çªå®€å
æ§å³ã§çµ±äžããšæèšããŠããã®ã¯çŸçãªçµ±äžæã§ã¯ãªããèªååãæç«ãããèšèšäžã®å¶çŽã§ããããŒãã远å ãããšãããçªãå¿
ãæ ã£ãŠããã»æè²å
æºãããããå®ãããšã§ãã¹ã¯ç²ŸåºŠãšæåä¿®æ£ã³ã¹ããäž¡ç«ããŸãã
12. ããŒããŒããããã®ååŸã¯14ã26pxãäžéã«ãã
gen_rain_glass_map.py L58ã® r = float(rng.uniform(14, 26))ãããã30pxè¶
ã«åºãããšåäœã®éšç²ãç®ç«ã¡ãããŠã30åã«ãŒãã§èŠãŠãããšãããäžèªç¶ã«æããŸãããASMRæ åãšããŠæ©èœããã®ã¯éšç²ãèæ¯ã«ãã質æãã§ã䞻圹ã«ãªã£ãŠã¯ãããªãã26pxãå®éšã§åºããäžéã§ããããè¶
ãããšèŠèŽè
ã³ã¡ã³ãã§ææãããããã«ãªããŸããã
13. YouTube ã¿ã€ãã«ã¯75æå以å ã§çµãã100æåã¯Pythonã¹ã©ã€ã¹ããµã€ã¬ã³ãã«åãæšãŠã
youtube_upload.py L63ã® spec["title"][:100] ã¯äŸå€ãæããŸãããmake_meta.py ã§ã¿ã€ãã«ãçµãéã«75æå以å
ãç®å®ã«ããããassert len(title) <= 80 ãå
¥ããŠãããšæ°ã¥ããŸãã
14. comfy_gen.py ã®ã¿ã€ã ã¢ãŠãã¯CPUã¢ãŒãã«åãã㊠600ç§ã«å»¶ã°ã
GPUåæã®èšèšã§ã¯300ç§ïŒdaily.sh L106ã® --timeout 300ïŒã§è¶³ããŸããã--cpu èµ·åïŒL53ïŒã®å®æ©ã§ã¯1024Ã576ã®çæã«3ã8åãããããšããããŸããCPUå°çšã§éçšãããªã --timeout 600 ã«å€æŽãã3åãªãã©ã€ïŒatt=0,1,2ïŒãå«ããæå€§åŸ
æ©æéã30å以å
ã«åãŸãããèšèšããŠãã ããã
ãŸãšã
ãã®ãã€ãã©ã€ã³ãæç«ããŠããçç±ãçªãè©°ãããšã3ã€ã®èšèšå€æã«åæããŸãã
ãåãå¿ èŠããªããã®ã¯åãããªãããRealVisXLãåºãã鿢ç»1æã䜿ãåããå€äœããããåå384æã®ãã£ãã·ã¥ã ãã§å šããŒãã«å¯Ÿå¿ããŸããGPUã¯ComfyUIã®ç»åçæã«ãã䜿ããªããæ®ãã¯CPUã®æ°å€èšç®ãšffmpegã®ãã£ã«ã¿åŠçã ãã§ããçæã³ã¹ããå®è³ªãŒãã«ãªã£ãã®ã¯ããããã§ãã
ãæ°åŠçã«æ£ããã«ãŒããã§ãªããã°éç£ã§ããªããspeed = n * span / TïŒnã¯æŽæ°ïŒãš wobnïŒ1ãŸãã¯2ã®æŽæ°ïŒãšãã2ã€ã®åšæå¶çŽãã16ç§ã«ãŒãã®å
é ãšæ«å°Ÿãå³å¯ã«äžèŽãããŸãããã®å¶çŽããªããã°30ååç»ã§112åç¹°ãè¿ãããç¶ãç®ãèŠèŠçãªæçµ¶ã«ãªããASMRãšããŠæ©èœããŸãããããããã«èŠãããããã§ã¯ãªããéç£ããŠãå£ããªããããã«æ°åŠããããŸãã
ãæ€èšŒã»åªçã»atomicãã®äžååã§ç¡äººéçšãå®ããåºåå4ç¹æ€èšŒïŒL179â186ïŒãåªçãã§ãã¯ïŒL87â88ïŒãatomic moveïŒL189â197ïŒã¯ã人éãæ¯æ¥ç¢ºèªããªããŠãå£ããåç»ãåºè·ãããªãããã®å®å
šè£
眮ã§ããlaunchdã7:00ã«èµ·åããŠ4ååŸã«ã¯ ~/Desktop/ASMR/ ã«å®æåãå±ããŠãããç§ãããããšã¯ç¿æYouTube StudioãéããŠå
¬éãã¿ã³ãæŒãã ãã§ãã
å幎éãã®ä»çµã¿ãåãç¶ããŠãASMRãã£ã³ãã«ã¯ããŸãææ¬¡åçã®å®å®ããæ±ã§ããGPU代ãŒããCC0鳿ºã®ã¿ãããŒã«ã«å®çµãåæå®è£ ã®é±æ«2æ¥ãé€ãã°ãç¶ç¶ã³ã¹ãã¯é»æ°ä»£ã ãã§ãã鿢ç»1æããæ¬ç©ã®éšãéãããæè¡ã¯ãæã£ãŠãããããã£ãšã·ã³ãã«ã§ããã
ä»çµã¿ã®å šäœåã»æ120äžã®å èš³ã»30æ¥æé ã¯æænoteã«ãŸãšããŠããŸãã ð Claude CodeèªåŸç°å¢ã§ãå®éã©ã皌ãã â ä»çµã¿ã»å®äŸã»å§ãæ¹ã»ãµããŒã
LilyïŒ@bokuwalilyïŒâ å人éçºè ãClaude Code ã§èªåååºç€ãçµã¿ãªãããiOSã¢ããªãWebãµãŒãã¹ãéç£ããŠããŸã
- AIã§ãå¯ãŠãŠãåãä»çµã¿ããäœã£ãŠæ120äžã«ãã話㯠noteã®ææèšäº ã«ð°
- OSS: github.com/bokuwalily ð
- ææ°æ å ±ã»ãåãåãã㯠X @bokuwalily ãžð
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ