Initial
This commit is contained in:
210
generate_story.sh
Executable file
210
generate_story.sh
Executable file
@@ -0,0 +1,210 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# generate_story.sh - Generate multi-scene AI videos with I2V chaining
|
||||
#
|
||||
# Usage: ./generate_story.sh <story_file> <output_dir> [options]
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Default settings
|
||||
WIDTH=1920
|
||||
HEIGHT=1088
|
||||
FRAMES=121
|
||||
STRENGTH=0.7
|
||||
FPS=24
|
||||
VENV_PYTHON="${VENV_PYTHON:-./venv/bin/python}"
|
||||
OUTPUT_DIR="./output"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
usage() {
|
||||
|
||||
echo "Usage: $0 <story_file> [output_dir] [options]"
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " story_file Text file with one prompt per line"
|
||||
echo " output_dir Directory to save output files (default: ./output)"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --width Video width (default: 1920)"
|
||||
echo " --height Video height (default: 1088)"
|
||||
echo " --frames Frames per scene (default: 121)"
|
||||
echo " --strength I2V conditioning strength 0.0-1.0 (default: 0.7)"
|
||||
echo " --fps Output framerate (default: 24)"
|
||||
echo " --python Python executable (default: python)"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " $0 stories/mountain.txt output/ --width 1024 --height 768"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
if [ $# -lt 1 ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
STORY_FILE="$1"
|
||||
shift 1
|
||||
|
||||
# Check if second arg is output dir (not an option starting with --)
|
||||
if [ $# -gt 0 ] && [[ "$1" != --* ]]; then
|
||||
OUTPUT_DIR="$1"
|
||||
shift 1
|
||||
fi
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--width) WIDTH="$2"; shift 2 ;;
|
||||
--height) HEIGHT="$2"; shift 2 ;;
|
||||
--frames) FRAMES="$2"; shift 2 ;;
|
||||
--strength) STRENGTH="$2"; shift 2 ;;
|
||||
--fps) FPS="$2"; shift 2 ;;
|
||||
--python) VENV_PYTHON="$2"; shift 2 ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Validate inputs
|
||||
if [ ! -f "$STORY_FILE" ]; then
|
||||
echo -e "${RED}Error: Story file not found: $STORY_FILE${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create output directory
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Read prompts into array (compatible with bash 3, skip comments and empty lines)
|
||||
PROMPTS=()
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
# Skip empty lines and comments
|
||||
[[ -z "$line" ]] && continue
|
||||
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
PROMPTS+=("$line")
|
||||
done < "$STORY_FILE"
|
||||
NUM_SCENES=${#PROMPTS[@]}
|
||||
|
||||
if [ $NUM_SCENES -eq 0 ]; then
|
||||
echo -e "${RED}Error: No prompts found in $STORY_FILE${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get story name from filename
|
||||
STORY_NAME=$(basename "$STORY_FILE" .txt)
|
||||
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo -e "${BLUE} mlx-video-maker${NC}"
|
||||
echo -e "${BLUE}========================================${NC}"
|
||||
echo ""
|
||||
echo -e "Story: ${GREEN}$STORY_NAME${NC}"
|
||||
echo -e "Scenes: ${GREEN}$NUM_SCENES${NC}"
|
||||
echo -e "Resolution: ${GREEN}${WIDTH}x${HEIGHT}${NC}"
|
||||
echo -e "Frames/scene: ${GREEN}$FRAMES${NC}"
|
||||
echo -e "I2V strength: ${GREEN}$STRENGTH${NC}"
|
||||
echo -e "Output: ${GREEN}$OUTPUT_DIR${NC}"
|
||||
echo ""
|
||||
|
||||
# Resolve VENV_PYTHON to absolute path before changing directories
|
||||
if [[ "$VENV_PYTHON" == ./* ]]; then
|
||||
VENV_PYTHON="$(pwd)/${VENV_PYTHON:2}"
|
||||
fi
|
||||
|
||||
# Generate scenes
|
||||
cd "$OUTPUT_DIR"
|
||||
|
||||
for i in $(seq 1 $NUM_SCENES); do
|
||||
IDX=$((i-1))
|
||||
PROMPT="${PROMPTS[$IDX]}"
|
||||
SCENE_FILE="scene${i}.mp4"
|
||||
|
||||
# Skip if already exists
|
||||
if [ -f "$SCENE_FILE" ]; then
|
||||
echo -e "${YELLOW}Scene $i already exists, skipping...${NC}"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}=== Scene $i / $NUM_SCENES ===${NC}"
|
||||
echo -e "${GREEN}Prompt:${NC} ${PROMPT:0:80}..."
|
||||
echo ""
|
||||
|
||||
if [ $i -eq 1 ]; then
|
||||
# First scene: Text-to-Video
|
||||
$VENV_PYTHON -m mlx_video.generate_av \
|
||||
--prompt "$PROMPT" \
|
||||
--height $HEIGHT \
|
||||
--width $WIDTH \
|
||||
--num-frames $FRAMES \
|
||||
--fps $FPS \
|
||||
--seed $((42 + i)) \
|
||||
--output-path "$SCENE_FILE"
|
||||
else
|
||||
# Subsequent scenes: Image-to-Video
|
||||
PREV=$((i-1))
|
||||
PREV_FILE="scene${PREV}.mp4"
|
||||
LAST_FRAME="scene${PREV}_lastframe.jpg"
|
||||
|
||||
# Extract last frame from previous scene
|
||||
if [ ! -f "$LAST_FRAME" ]; then
|
||||
echo -e "${YELLOW}Extracting last frame from scene $PREV...${NC}"
|
||||
FRAME_COUNT=$(ffprobe -v error -select_streams v:0 -count_frames \
|
||||
-show_entries stream=nb_read_frames \
|
||||
-of default=nokey=1:noprint_wrappers=1 "$PREV_FILE")
|
||||
LAST_IDX=$((FRAME_COUNT - 1))
|
||||
ffmpeg -i "$PREV_FILE" -vf "select=eq(n\\,$LAST_IDX)" \
|
||||
-vframes 1 -q:v 2 "$LAST_FRAME" -y 2>/dev/null
|
||||
fi
|
||||
|
||||
# Generate with I2V
|
||||
$VENV_PYTHON -m mlx_video.generate_av \
|
||||
--prompt "$PROMPT" \
|
||||
--image "$LAST_FRAME" \
|
||||
--image-strength $STRENGTH \
|
||||
--height $HEIGHT \
|
||||
--width $WIDTH \
|
||||
--num-frames $FRAMES \
|
||||
--fps $FPS \
|
||||
--seed $((42 + i)) \
|
||||
--output-path "$SCENE_FILE"
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Scene $i complete!${NC}"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}=== Concatenating final movie ===${NC}"
|
||||
|
||||
# Create concat list
|
||||
CONCAT_LIST="concat_list.txt"
|
||||
> "$CONCAT_LIST"
|
||||
for i in $(seq 1 $NUM_SCENES); do
|
||||
if [ -f "scene${i}.mp4" ]; then
|
||||
echo "file 'scene${i}.mp4'" >> "$CONCAT_LIST"
|
||||
fi
|
||||
done
|
||||
|
||||
# Concatenate with high quality encoding
|
||||
FINAL_FILE="${STORY_NAME}.mp4"
|
||||
ffmpeg -f concat -safe 0 -i "$CONCAT_LIST" \
|
||||
-c:v libx264 -crf 18 -preset slow \
|
||||
-c:a aac -b:a 192k \
|
||||
"$FINAL_FILE" -y
|
||||
|
||||
# Get duration
|
||||
DURATION=$(ffprobe -v error -show_entries format=duration \
|
||||
-of default=noprint_wrappers=1:nokey=1 "$FINAL_FILE")
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo -e "${GREEN} Complete!${NC}"
|
||||
echo -e "${GREEN}========================================${NC}"
|
||||
echo ""
|
||||
echo -e "Final movie: ${BLUE}$(pwd)/$FINAL_FILE${NC}"
|
||||
echo -e "Duration: ${BLUE}${DURATION%.*} seconds${NC}"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user