[feat] update-video.py
This commit is contained in:
17
.idea/.idea.Abyss/.idea/workspace.xml
generated
17
.idea/.idea.Abyss/.idea/workspace.xml
generated
@@ -10,21 +10,8 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="bf317275-3039-49bb-a475-725a800a0cce" name="Changes" comment="">
|
<list default="true" id="bf317275-3039-49bb-a475-725a800a0cce" name="Changes" comment="">
|
||||||
<change afterPath="$PROJECT_DIR$/Abyss/Toolkits/image-creator.py" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/Abyss/Toolkits/image-sum.py" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/Abyss/Toolkits/update-tags.py" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/Abyss/Toolkits/update-video.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/.idea.Abyss/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.Abyss/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/.idea.Abyss/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.Abyss/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Abyss.sln.DotSettings.user" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss.sln.DotSettings.user" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/Abyss/Toolkits/update-video.py" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Toolkits/update-video.py" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/Abyss/Components/Controllers/Media/ImageController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Controllers/Media/ImageController.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/Abyss/Components/Controllers/Media/VideoController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Controllers/Media/VideoController.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/Abyss/Components/Services/ConfigureService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Services/ConfigureService.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/Abyss/Components/Services/ResourceService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Services/ResourceService.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/Abyss/Components/Services/UserService.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Services/UserService.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/Abyss/Components/Tools/NaturalStringComparer.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Components/Tools/NaturalStringComparer.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/Abyss/Model/Bookmark.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Model/Bookmark.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/Abyss/Model/Comic.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Model/Comic.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/Abyss/Properties/launchSettings.json" beforeDir="false" afterPath="$PROJECT_DIR$/Abyss/Properties/launchSettings.json" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -92,7 +79,7 @@
|
|||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"XThreadsFramesViewSplitterKey": "0.30266345",
|
"XThreadsFramesViewSplitterKey": "0.30266345",
|
||||||
"git-widget-placeholder": "dev-task",
|
"git-widget-placeholder": "main",
|
||||||
"last_opened_file_path": "/storage/Images/31/summary.json",
|
"last_opened_file_path": "/storage/Images/31/summary.json",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ def get_video_duration(video_path):
|
|||||||
"""Get video duration in milliseconds using ffprobe"""
|
"""Get video duration in milliseconds using ffprobe"""
|
||||||
try:
|
try:
|
||||||
cmd = [
|
cmd = [
|
||||||
'ffprobe',
|
'ffprobe',
|
||||||
'-v', 'error',
|
'-v', 'error',
|
||||||
'-show_entries', 'format=duration',
|
'-show_entries', 'format=duration',
|
||||||
'-of', 'default=noprint_wrappers=1:nokey=1',
|
'-of', 'default=noprint_wrappers=1:nokey=1',
|
||||||
@@ -44,15 +44,15 @@ def create_thumbnails(video_path, gallery_path, num_thumbnails=10):
|
|||||||
except (subprocess.CalledProcessError, ValueError) as e:
|
except (subprocess.CalledProcessError, ValueError) as e:
|
||||||
print(f"Could not get duration for '{video_path}': {e}. Skipping thumbnail creation.")
|
print(f"Could not get duration for '{video_path}': {e}. Skipping thumbnail creation.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if duration <= 0:
|
if duration <= 0:
|
||||||
print(f"Warning: Invalid video duration for '{video_path}'. Skipping thumbnail creation.")
|
print(f"Warning: Invalid video duration for '{video_path}'. Skipping thumbnail creation.")
|
||||||
return
|
return
|
||||||
|
|
||||||
interval = duration / (num_thumbnails + 1)
|
interval = duration / (num_thumbnails + 1)
|
||||||
|
|
||||||
print(f"Generating {num_thumbnails} thumbnails for {video_path.name}...")
|
print(f"Generating {num_thumbnails} thumbnails for {video_path.name}...")
|
||||||
|
|
||||||
for i in range(num_thumbnails):
|
for i in range(num_thumbnails):
|
||||||
timestamp = (i + 1) * interval
|
timestamp = (i + 1) * interval
|
||||||
output_thumbnail_path = gallery_path / f"{i}.jpg"
|
output_thumbnail_path = gallery_path / f"{i}.jpg"
|
||||||
@@ -68,6 +68,45 @@ def create_thumbnails(video_path, gallery_path, num_thumbnails=10):
|
|||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f" Error extracting thumbnail {i}.jpg: {e}")
|
print(f" Error extracting thumbnail {i}.jpg: {e}")
|
||||||
|
|
||||||
|
def create_cover(video_path, output_path, time_percent):
|
||||||
|
"""
|
||||||
|
Creates a cover image from a video at a specified time percentage.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
subprocess.run(['ffmpeg', '-version'], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
|
print("Error: ffmpeg is not installed or not in your PATH. Cannot create cover.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
duration_cmd = [
|
||||||
|
'ffprobe', '-v', 'error', '-show_entries', 'format=duration',
|
||||||
|
'-of', 'default=noprint_wrappers=1:nokey=1', str(video_path)
|
||||||
|
]
|
||||||
|
result = subprocess.run(duration_cmd, capture_output=True, text=True, check=True)
|
||||||
|
duration = float(result.stdout)
|
||||||
|
except (subprocess.CalledProcessError, ValueError) as e:
|
||||||
|
print(f"Could not get duration for '{video_path}': {e}. Cannot create cover.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if duration <= 0:
|
||||||
|
print(f"Warning: Invalid video duration for '{video_path}'. Cannot create cover.")
|
||||||
|
return
|
||||||
|
|
||||||
|
timestamp = duration * time_percent
|
||||||
|
|
||||||
|
ffmpeg_cmd = [
|
||||||
|
'ffmpeg', '-ss', str(timestamp), '-i', str(video_path),
|
||||||
|
'-vframes', '1', str(output_path), '-y'
|
||||||
|
]
|
||||||
|
|
||||||
|
print(f"Creating cover image from video at {timestamp:.2f} seconds...")
|
||||||
|
try:
|
||||||
|
subprocess.run(ffmpeg_cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
print(f"Cover image created at {output_path}")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Error creating cover image: {e}")
|
||||||
|
|
||||||
def update_summary(base_path, name_input=None, author_input=None):
|
def update_summary(base_path, name_input=None, author_input=None):
|
||||||
"""
|
"""
|
||||||
Updates the summary.json file for a given path.
|
Updates the summary.json file for a given path.
|
||||||
@@ -87,7 +126,7 @@ def update_summary(base_path, name_input=None, author_input=None):
|
|||||||
"like": 0,
|
"like": 0,
|
||||||
"author": author_input if author_input is not None else "anonymous"
|
"author": author_input if author_input is not None else "anonymous"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Load existing summary if available
|
# Load existing summary if available
|
||||||
if summary_path.exists():
|
if summary_path.exists():
|
||||||
try:
|
try:
|
||||||
@@ -99,13 +138,13 @@ def update_summary(base_path, name_input=None, author_input=None):
|
|||||||
default_summary[key] = existing_data[key]
|
default_summary[key] = existing_data[key]
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
print("Warning: Invalid JSON in summary.json, using defaults")
|
print("Warning: Invalid JSON in summary.json, using defaults")
|
||||||
|
|
||||||
# Update duration from video file
|
# Update duration from video file
|
||||||
if video_path.exists():
|
if video_path.exists():
|
||||||
default_summary["duration"] = get_video_duration(video_path)
|
default_summary["duration"] = get_video_duration(video_path)
|
||||||
else:
|
else:
|
||||||
print(f"Warning: video.mp4 not found at {video_path}")
|
print(f"Warning: video.mp4 not found at {video_path}")
|
||||||
|
|
||||||
# Update gallery from directory
|
# Update gallery from directory
|
||||||
if gallery_path.exists() and gallery_path.is_dir():
|
if gallery_path.exists() and gallery_path.is_dir():
|
||||||
gallery_files = []
|
gallery_files = []
|
||||||
@@ -116,11 +155,11 @@ def update_summary(base_path, name_input=None, author_input=None):
|
|||||||
default_summary["gallery"] = gallery_files
|
default_summary["gallery"] = gallery_files
|
||||||
else:
|
else:
|
||||||
print(f"Warning: gallery directory not found at {gallery_path}")
|
print(f"Warning: gallery directory not found at {gallery_path}")
|
||||||
|
|
||||||
# Write updated summary
|
# Write updated summary
|
||||||
with open(summary_path, 'w', encoding='utf-8') as f:
|
with open(summary_path, 'w', encoding='utf-8') as f:
|
||||||
json.dump(default_summary, f, indent=4, ensure_ascii=False)
|
json.dump(default_summary, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
print(f"Summary updated successfully at {summary_path}")
|
print(f"Summary updated successfully at {summary_path}")
|
||||||
|
|
||||||
def find_next_directory(base_path):
|
def find_next_directory(base_path):
|
||||||
@@ -129,7 +168,7 @@ def find_next_directory(base_path):
|
|||||||
for item in base_path.iterdir():
|
for item in base_path.iterdir():
|
||||||
if item.is_dir() and item.name.isdigit():
|
if item.is_dir() and item.name.isdigit():
|
||||||
existing_dirs.add(int(item.name))
|
existing_dirs.add(int(item.name))
|
||||||
|
|
||||||
next_num = 1
|
next_num = 1
|
||||||
while next_num in existing_dirs:
|
while next_num in existing_dirs:
|
||||||
next_num += 1
|
next_num += 1
|
||||||
@@ -141,8 +180,9 @@ def main():
|
|||||||
print("Commands:")
|
print("Commands:")
|
||||||
print(" -u <path> Update the summary.json in the specified path.")
|
print(" -u <path> Update the summary.json in the specified path.")
|
||||||
print(" -a <video_file> <path> Add a new video project in a new directory under the specified path.")
|
print(" -a <video_file> <path> Add a new video project in a new directory under the specified path.")
|
||||||
|
print(" -c <path> <time> Create a cover image from the video in the specified path at a given time percentage (0.0-1.0).")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
command = sys.argv[1]
|
command = sys.argv[1]
|
||||||
|
|
||||||
if command == '-u':
|
if command == '-u':
|
||||||
@@ -159,7 +199,7 @@ def main():
|
|||||||
if len(sys.argv) != 4:
|
if len(sys.argv) != 4:
|
||||||
print("Usage: python script.py -a <video_file> <path>")
|
print("Usage: python script.py -a <video_file> <path>")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
video_source_path = Path(sys.argv[2])
|
video_source_path = Path(sys.argv[2])
|
||||||
base_path = Path(sys.argv[3])
|
base_path = Path(sys.argv[3])
|
||||||
|
|
||||||
@@ -170,37 +210,66 @@ def main():
|
|||||||
if not base_path.is_dir():
|
if not base_path.is_dir():
|
||||||
print(f"Error: Base path not found or is not a directory: {base_path}")
|
print(f"Error: Base path not found or is not a directory: {base_path}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Find a new directory name (e.g., "1", "2", "3")
|
# Find a new directory name (e.g., "1", "2", "3")
|
||||||
new_dir_name = find_next_directory(base_path)
|
new_dir_name = find_next_directory(base_path)
|
||||||
new_project_path = base_path / new_dir_name
|
new_project_path = base_path / new_dir_name
|
||||||
|
|
||||||
# Create the new project directory and the gallery subdirectory
|
# Create the new project directory and the gallery subdirectory
|
||||||
new_project_path.mkdir(exist_ok=True)
|
new_project_path.mkdir(exist_ok=True)
|
||||||
gallery_path = new_project_path / "gallery"
|
gallery_path = new_project_path / "gallery"
|
||||||
gallery_path.mkdir(exist_ok=True)
|
gallery_path.mkdir(exist_ok=True)
|
||||||
print(f"New project directory created at {new_project_path}")
|
print(f"New project directory created at {new_project_path}")
|
||||||
|
|
||||||
# Copy video file to the new directory
|
# Copy video file to the new directory
|
||||||
shutil.copy(video_source_path, new_project_path / "video.mp4")
|
shutil.copy(video_source_path, new_project_path / "video.mp4")
|
||||||
print(f"Video copied to {new_project_path / 'video.mp4'}")
|
print(f"Video copied to {new_project_path / 'video.mp4'}")
|
||||||
|
|
||||||
# --- 新增功能:自动生成缩略图 ---
|
# Auto-generate thumbnails
|
||||||
video_dest_path = new_project_path / "video.mp4"
|
video_dest_path = new_project_path / "video.mp4"
|
||||||
create_thumbnails(video_dest_path, gallery_path)
|
create_thumbnails(video_dest_path, gallery_path)
|
||||||
# ------------------------------------
|
|
||||||
|
|
||||||
# Get user input for name and author
|
# Get user input for name and author, with a prompt for default values
|
||||||
video_name = input("Enter the video name: ")
|
print("\nEnter the video name (press Enter to use the original filename):")
|
||||||
video_author = input("Enter the author's name: ")
|
video_name = input(f"Video Name [{video_source_path.stem}]: ")
|
||||||
|
if not video_name:
|
||||||
|
video_name = video_source_path.stem
|
||||||
|
|
||||||
# Update the summary with user input
|
print("\nEnter the author's name (press Enter to use 'Anonymous'):")
|
||||||
|
video_author = input("Author Name [Anonymous]: ")
|
||||||
|
if not video_author:
|
||||||
|
video_author = "Anonymous"
|
||||||
|
|
||||||
|
# Update the summary with user input or default values
|
||||||
update_summary(new_project_path, name_input=video_name, author_input=video_author)
|
update_summary(new_project_path, name_input=video_name, author_input=video_author)
|
||||||
|
|
||||||
|
elif command == '-c':
|
||||||
|
if len(sys.argv) != 4:
|
||||||
|
print("Usage: python script.py -c <path> <time>")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
base_path = Path(sys.argv[2])
|
||||||
|
video_path = base_path / "video.mp4"
|
||||||
|
cover_path = base_path / "cover.jpg"
|
||||||
|
|
||||||
|
try:
|
||||||
|
time_percent = float(sys.argv[3])
|
||||||
|
if not 0.0 <= time_percent <= 1.0:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError:
|
||||||
|
print("Error: Time value must be a number between 0.0 and 1.0.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not video_path.exists() or not video_path.is_file():
|
||||||
|
print(f"Error: video.mp4 not found at {video_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
create_cover(video_path, cover_path, time_percent)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("Invalid command. Use -u or -a.")
|
print("Invalid command. Use -u, -a, or -c.")
|
||||||
print("Usage: python script.py <command> [arguments]")
|
print("Usage: python script.py <command> [arguments]")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
Reference in New Issue
Block a user