diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8f7ea6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +venv/ +__pycache__/ +*.pyc +*.pyo +*.log +build/ +dist/ +*.spec +Return haunted farmhouse Part 1.mp4 diff --git a/.sources_library/davinci_resolve_20_2_2_info.md b/.sources_library/davinci_resolve_20_2_2_info.md new file mode 100644 index 0000000..303cb68 --- /dev/null +++ b/.sources_library/davinci_resolve_20_2_2_info.md @@ -0,0 +1,91 @@ +About DaVinci Resolve 20 +Welcome to DaVinci Resolve 20.2.2! + +This installer will add DaVinci Resolve software to your system as well as additional software for remote monitoring and utilities for setting up hardware control panels. It also installs Blackmagic RAW Player and Blackmagic Proxy Generator into the applications folder. + +The free version of DaVinci Resolve 20 includes all of the same high quality processing as DaVinci Resolve 20 Studio and can handle unlimited resolution media files. However it does limit project mastering and output to Ultra HD resolutions or lower and only supports a single processing GPU on Windows and Linux. + +If you need features such as support for multiple GPUs, 4K output, motion blur effects, temporal and spatial noise reduction, multiple AI-based tools, HDR tools, camera tracker, voice isolation, surround sound and immersive audio, multiple Resolve FX, 3D stereoscopic tools and remote rendering, please upgrade to DaVinci Resolve 20 Studio. + +We hope you do decide to upgrade as your facility grows and you do more advanced work! +Installing DaVinci Resolve Software on MacOS + +Double-click the DaVinci Resolve Installer icon and follow the onscreen instructions. To remove DaVinci Resolve from your system, double-click the Uninstall Resolve icon. +Installing DaVinci Resolve Software on Windows + +Double-click the DaVinci Resolve Installer icon and follow the onscreen instructions. To remove DaVinci Resolve from your system, go to the Programs and Features control panel, select DaVinci Resolve, click on Uninstall and follow the onscreen prompts. +Installing DaVinci Resolve Software on Linux + +Double-click the DaVinci Resolve installer and follow the onscreen instructions. To remove DaVinci Resolve from your system, select the uninstall option after running the installer. + +For DaVinci Resolve 20.2.2, we have taken efforts to keep the project libraries compatible with DaVinci Resolve 19.1.4. While this allows you to access the project library with 19.1.4, individual projects created or opened in 20.2.2 will no longer be accessible in 19.1.4. We recommend a full project library backup as well as individual project backups before opening projects in 20.2.2. +What's new in DaVinci Resolve 20.2.2 + +The following features have been added or updated. + + Improved Mac viewer color management for Rec.709 scene. + Addressed intermittent jittery playback on Fairlight. + Addressed trim edits sometimes removing linked audio. + Addressed ripple deletes sometimes unlinking clips. + AI IntelliScript now works with multicam clips. + Addressed go to mouse pointer not working in the cinema viewer. + Addressed issue with go to mouse pointer in fixed playhead mode. + Addressed an issue renaming generators in inspector. + Addressed issue with blade hover preview in edit timeline. + Addressed a crash with some ripple trim operations in edit page. + Addressed incorrect track clip counts for timeline subclips. + Apply grade from clip now honors node stack grade preference. + Addressed issue editing Fusion spline ease in values. + Addressed issue when loading some USD texture paths. + Addressed viewing named layers from Renderer3D in Fusion. + Addressed net rendering of some Swizzler comps. + Addressed issues with audio stuttering on some mp3 files. + Addressed Fairlight viewer in out controls hidden at certain widths. + Addressed issue with R3D artefacts when using motion blend. + Addressed EXR to ProRes transcodes losing immersive metadata. + Scripting API support to set media location on project creation. + Scripting API support to query and apply Fairlight presets. + Addressed issues querying some render formats from scripts. + Right click on media metadata to copy file metadata values. + General performance and stability improvements. + +Minimum system requirements for Mac OS + + macOS 14 Sonoma or later. + 8 GB of system memory or 16 GB when using Fusion. + For monitoring, Blackmagic Design Desktop Video 12.9 or later. + Apple Silicon based computer or GPU which supports Metal. + +Minimum system requirements for Windows + + Windows 10 Creators Update. + 16 GB of system memory or 32 GB when using Fusion. + For monitoring, Blackmagic Design Desktop Video 12.9 or later. + Integrated GPU or discrete GPU with at least 4 GB of VRAM. + GPU which supports OpenCL 1.2 or CUDA 12.8. + AMD/Intel official drivers from your GPU manufacturer. + NVIDIA Studio driver 570.65 or newer. + +Minimum system requirements for Windows for Arm + + Windows 11 for ARM. + Qualcomm Snapdragon X Elite series processor. + 16 GB of system memory or 32 GB for 4K or when using Fusion. + +Minimum system requirements for Linux + + Rocky Linux 8.6. + 32 GB of system memory. + For monitoring, Blackmagic Design Desktop Video 12.9 or later. + Discrete GPU with at least 4 GB of VRAM. + GPU which supports OpenCL 1.2 or CUDA 12.8. + AMD official drivers from your GPU manufacturer. + NVIDIA Studio driver 570.26 or newer. + +Additional Information + +You will also need to download and install the latest Blackmagic Design Desktop Video software for monitoring with your Blackmagic Design video hardware. Desktop Video is available from www.blackmagicdesign.com/support. + +© 2001-2025 Blackmagic Design Pty. Ltd. All rights reserved. Blackmagic Design, Blackmagic, DeckLink, Multibridge, Intensity, H.264 Pro Recorder and "Leading the creative video revolution" are trademarks of Blackmagic Design Pty. Ltd., registered in the U.S.A and other countries. + +Updated October 14, 2025. diff --git a/.sources_library/davinci_resolve_20_2_new_features.pdf b/.sources_library/davinci_resolve_20_2_new_features.pdf new file mode 100644 index 0000000..f4b8147 Binary files /dev/null and b/.sources_library/davinci_resolve_20_2_new_features.pdf differ diff --git a/.sources_library/davinci_resolve_codecs.pdf b/.sources_library/davinci_resolve_codecs.pdf new file mode 100644 index 0000000..b63b10a Binary files /dev/null and b/.sources_library/davinci_resolve_codecs.pdf differ diff --git a/.sources_library/yt-dlp_readme.md b/.sources_library/yt-dlp_readme.md new file mode 100644 index 0000000..9838867 --- /dev/null +++ b/.sources_library/yt-dlp_readme.md @@ -0,0 +1,4 @@ +I have successfully retrieved the content of the `yt-dlp` GitHub repository's README. It contains detailed information about the `yt-dlp` command-line audio/video downloader, including installation instructions, usage examples, various options for network, geo-restriction, video selection, download, filesystem, thumbnail, internet shortcut, verbosity, workarounds, video format, subtitle, authentication, and post-processing. It also covers changes from `youtube-dl`, contributing guidelines, and a FAQ. + +Sources: +[1] yt-dlp/yt-dlp: A feature-rich command-line audio/video ... - GitHub (https://github.com/yt-dlp/yt-dlp) \ No newline at end of file diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index 7250148..d337f9a 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -1,12 +1,15 @@ # Video Converter Script Development Plan ## 1. Project Goal -Create a Python script to convert video files into formats highly compatible with Davinci Resolve, ensuring optimal aspect ratio, video quality, and audio fidelity. The script should be user-friendly, prompting for input and output locations, and leveraging `ffmpeg` for robust conversion. +Create a standalone executable application (using Python) to convert video files into formats highly compatible with Davinci Resolve, ensuring optimal aspect ratio, video quality, and audio fidelity. The application should be user-friendly, accepting input file paths via CLI arguments, prompting for output locations (if not specified), and leveraging `ffmpeg` for robust conversion. The goal is a single executable that requires no external dependencies (like Python or `ffmpeg` installations) from the end-user. ## 2. Key Tools and Technologies * **Python:** For scripting the user interface, logic, and orchestrating `ffmpeg` commands. -* **`ffmpeg`:** The primary tool for video and audio conversion. It will be called via Python's `subprocess` module. -* **`ffprobe` (part of `ffmpeg` suite):** For analyzing input video file properties (codecs, resolution, aspect ratio, audio streams). + * **`ffmpeg-python` library:** A robust Python binding for FFmpeg to simplify command construction and execution. +* **`ffmpeg` & `ffprobe`:** The primary tools for video and audio conversion and analysis. These will be bundled with the final application. +* **PyInstaller (or similar):** For packaging the Python script and bundled `ffmpeg`/`ffprobe` into a standalone executable. + +**Inspiration Repositories:** We will review `xavier150/convert-video-for-Resolve` and `tkmxqrdxddd/davinci-video-converter` for insights into Davinci Resolve specific conversion strategies and `ffmpeg` command construction. * **`yt-dlp` (Optional, for future expansion):** While the current scope is local files, `yt-dlp` could be integrated later for direct downloading and converting from online sources. ## 3. Core Functionality and Requirements @@ -14,7 +17,7 @@ Create a Python script to convert video files into formats highly compatible wit ### 3.1. User Interaction * **Input File Selection:** The script will prompt the user to provide the absolute path to the input video file. * **Output Directory Selection:** The script will ask the user for a desired output directory. If no directory is provided, it will default to the script's current working directory. -* **Output File Naming:** Converted files will be named clearly, possibly by appending `_DR_compatible` or similar to the original filename, while retaining the new extension. +* **Output File Naming:** Converted files will be named clearly. If the output directory is the same as the input file's directory, the output filename will be prefixed with `recoded_` (e.g., `recoded_original_file.mov`). Otherwise, a clear naming convention will be applied (e.g., append `_DR_compatible`). ### 3.2. Shell Environment (Re-evaluation) * The initial request mentioned confirming the shell type. However, `ffmpeg` commands executed via Python's `subprocess` module are generally shell-agnostic. The Python script itself will handle the execution. Therefore, explicit shell detection is likely unnecessary unless specific shell-dependent environment variables or configurations for `ffmpeg` or `yt-dlp` (if integrated later) become an issue. This point will be kept in mind for troubleshooting. @@ -76,12 +79,60 @@ Based on the "DaVinci Resolve 18 Supported Codec List.pdf" and web search result 5. **`ffmpeg` Execution:** Use `subprocess.run()` to execute the `ffmpeg` command, capturing stdout/stderr for logging. 6. **Error Handling:** Add `try-except` blocks for file operations, `subprocess` calls, and `ffprobe` parsing. 7. **Basic Testing:** Test with a few sample video files (e.g., H.264/AAC MP4) to ensure conversion to DNxHR/PCM MOV works. +8. **Packaging and Bundling:** Use PyInstaller (or similar) to create standalone executables for target operating systems. This step will involve bundling the Python script, its dependencies, and the `ffmpeg`/`ffprobe` binaries. Note that packaging will be OS-specific (e.g., a Linux executable must be built on Linux). +9. **Error Handling for Bundled Tools:** Implement checks to ensure bundled `ffmpeg`/`ffprobe` are accessible and provide clear error messages if not. + +## 6. Directory and File Structure + +To ensure a modular, maintainable, and scalable project, the following directory and file structure is proposed: + +``` +/home/joe/Cloud9/Documents/Obisdian/projects/Video Converter/ +├── .sources_library/ +│ ├── davinci_resolve_18_supported_codec_list.pdf +│ ├── davinci_resolve_20_2_2_info.md +│ ├── davinci_resolve_20_2_new_features.pdf +│ └── yt-dlp_readme.md +├── src/ +│ ├── __init__.py # Makes 'src' a Python package +│ ├── main.py # Main CLI entry point, argument parsing +│ ├── converter.py # Core video conversion logic, ffmpeg calls +│ ├── utils.py # Helper functions (ffprobe parsing, path handling, logging) +│ └── config.py # Configuration settings (codec profiles, defaults) +├── tests/ +│ ├── __init__.py +│ ├── test_converter.py # Unit tests for converter.py +│ └── test_utils.py # Unit tests for utils.py +├── .gitignore +├── DEVELOPMENT_PLAN.md +├── README.md +├── requirements.txt # Python dependencies (e.g., ffmpeg-python) +└── build/ # Output directory for packaged executables (PyInstaller) +``` + +**Explanation of Structure:** + +* **`.sources_library/`**: Contains all reference documents and downloaded resources. +* **`src/`**: This directory will contain all the Python source code for the application. + * `__init__.py`: Marks `src` as a Python package. + * `main.py`: The primary entry point for the CLI. It will handle argument parsing (`argparse`) and orchestrate calls to `converter.py` and `utils.py`. + * `converter.py`: This module will encapsulate the core logic for video conversion. It will contain functions that take input file paths, desired output settings, and construct/execute `ffmpeg` commands using `ffmpeg-python`. + * `utils.py`: This module will house helper functions, such as: + * Parsing `ffprobe` output. + * Handling file path manipulations (e.g., adding `recoded_` prefix, resolving output paths). + * Basic file existence checks. + * Logging setup. + * `config.py`: For storing configurable parameters like default codec profiles, output formats, or any other settings that might be user-adjustable or need to be easily changed. +* **`tests/`**: This directory will contain unit tests for the `src` modules. + * `__init__.py`: Marks `tests` as a Python package. + * `test_converter.py`: Tests for the conversion logic in `converter.py`. + * `test_utils.py`: Tests for utility functions in `utils.py`. +* **`.gitignore`**: Standard Git ignore file. +* **`DEVELOPMENT_PLAN.md`**: This detailed plan document. +* **`README.md`**: Project overview. +* **`requirements.txt`**: Will list all Python dependencies (e.g., `ffmpeg-python`). This is crucial for managing the project's environment. +* **`build/`**: This directory will be created later by PyInstaller to store the packaged executables. + +This structure promotes modularity, making the codebase easier to understand, test, and maintain. It separates concerns (CLI handling, core logic, utilities, configuration) into distinct modules. + -## 5. Future Considerations / Enhancements -* **GUI:** Develop a simple graphical user interface for easier interaction. -* **Batch Processing:** Allow conversion of multiple files at once. -* **Configuration File:** Enable users to save preferred codec/profile settings. -* **Progress Bar:** Implement a progress indicator for long conversions. -* **`yt-dlp` Integration:** Add an option to download videos directly from URLs before converting. -* **Detailed Logging:** Enhance logging for debugging and user feedback. -* **Pre-defined Profiles:** Offer specific profiles for different Davinci Resolve versions or common use cases (e.g., "YouTube Upload," "Archival Quality"). diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cfbf779 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +ffmpeg-python==0.2.0 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..096daeb --- /dev/null +++ b/src/config.py @@ -0,0 +1,34 @@ +import os + +# Default video and audio settings for DaVinci Resolve compatibility (Free Linux version) +# Based on DaVinci Resolve 18 Supported Codec List and web search recommendations. + +# Target 1080p60 and 1440p60 + +DEFAULT_VIDEO_CODEC = "dnxhd" +DEFAULT_VIDEO_PROFILE_1080P = "HQ" # DNxHD HQ for 1080p +DEFAULT_VIDEO_PROFILE_1440P = "HQ" # DNxHR HQ for 1440p +DEFAULT_VIDEO_PIX_FMT = "yuv422p" # Common for DNxHD/HR +DEFAULT_CONTAINER = ".mov" + +DEFAULT_AUDIO_CODEC = "pcm_s16le" # Linear PCM 16-bit little-endian + +# FFmpeg global options +FFMPEG_GLOBAL_OPTIONS = [ + "-hide_banner", + "-loglevel", "info", +] + +# FFprobe global options +FFPROBE_GLOBAL_OPTIONS = [ + "-v", "error", +] + +# Path to bundled ffmpeg/ffprobe binaries (to be set by PyInstaller hook or similar) +# For development, assume they are in PATH +FFMPEG_PATH = os.environ.get("FFMPEG_PATH", "ffmpeg") +FFPROBE_PATH = os.environ.get("FFPROBE_PATH", "ffprobe") + +# Output file naming conventions +OUTPUT_SUFFIX = "_DR_compatible" +OUTPUT_PREFIX = "recoded_" diff --git a/src/converter.py b/src/converter.py new file mode 100644 index 0000000..efec257 --- /dev/null +++ b/src/converter.py @@ -0,0 +1,99 @@ +import ffmpeg +import os +import sys + +from . import utils +from . import config + +def convert_video( + input_file_path, + output_dir, + video_codec=config.DEFAULT_VIDEO_CODEC, + audio_codec=config.DEFAULT_AUDIO_CODEC, + output_suffix=config.OUTPUT_SUFFIX, + output_prefix=config.OUTPUT_PREFIX, + output_ext=config.DEFAULT_CONTAINER +): + """Converts a video file to a DaVinci Resolve compatible format.""" + print(f"Analyzing input file: {input_file_path}") + video_info = utils.get_video_info(input_file_path) + audio_info = utils.get_audio_info(input_file_path) + + if not video_info or not audio_info: + print("Error: Could not retrieve full media information. Aborting conversion.", file=sys.stderr) + return False + + # Extract relevant video stream info + try: + v_stream = video_info["streams"][0] + width = v_stream["width"] + height = v_stream["height"] + # frame_rate = eval(v_stream["avg_frame_rate"]) # avg_frame_rate is a string like '30/1' + # duration = float(v_stream["duration_ts"]) / frame_rate + # pix_fmt = v_stream["pix_fmt"] + except (KeyError, IndexError) as e: + print(f"Error parsing video stream info: {e}", file=sys.stderr) + return False + + # Extract relevant audio stream info + try: + a_stream = audio_info["streams"][0] + # a_codec_name = a_stream["codec_name"] + # sample_rate = a_stream["sample_rate"] + # channels = a_stream["channels"] + except (KeyError, IndexError) as e: + print(f"Error parsing audio stream info: {e}", file=sys.stderr) + # This might be a video-only file, proceed with no audio conversion + audio_codec = None + + # Determine video profile based on resolution + video_profile = "dnxhr_sq" # Default to 1080p Standard Quality + if height > 1080: + video_profile = "dnxhr_hq" # For 1440p High Quality + + output_file_path = utils.generate_output_path( + input_file_path, output_dir, output_suffix, output_prefix, output_ext + ) + + print(f"Converting '{input_file_path}' to '{output_file_path}'") + + try: + stream = ffmpeg.input(input_file_path) + + # Video stream setup + video_stream = stream.video + video_options = { + "c:v": video_codec, + "profile:v": video_profile, + "pix_fmt": config.DEFAULT_VIDEO_PIX_FMT, + } + + # Audio stream setup + audio_stream = None + audio_options = {} + if audio_codec: + audio_stream = stream.audio + audio_options = {"c:a": audio_codec} + + # Construct output stream + if audio_stream: + out = ffmpeg.output(video_stream, audio_stream, output_file_path, **video_options, **audio_options) + else: + out = ffmpeg.output(video_stream, output_file_path, **video_options) + + # Add global options and run + out = ffmpeg.overwrite_output(out) + ffmpeg.run(out, cmd=config.FFMPEG_PATH, capture_stdout=True, capture_stderr=True) + + print(f"Successfully converted to '{output_file_path}'") + return True + + except ffmpeg.Error as e: + print(f"FFmpeg Error: {e.stderr.decode()}", file=sys.stderr) + return False + except FileNotFoundError: + print("Error: ffmpeg not found. Please ensure FFmpeg is installed and in your PATH.", file=sys.stderr) + return False + except Exception as e: + print(f"An unexpected error occurred during conversion: {e}", file=sys.stderr) + return False diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..8391bd9 --- /dev/null +++ b/src/main.py @@ -0,0 +1,58 @@ +import argparse +import os +import sys + +# Assuming these modules will be created and populated later +from . import converter +from . import utils +from . import config + +def main(): + parser = argparse.ArgumentParser( + description="Convert video files to DaVinci Resolve compatible formats." + ) + parser.add_argument( + "input_file", + help="Path to the input video file.", + type=str + ) + parser.add_argument( + "-o", "--output_dir", + help="Optional: Directory to save the converted file. Defaults to the input file's directory.", + type=str, + default=None + ) + + args = parser.parse_args() + + input_file_path = os.path.abspath(args.input_file) + + if not os.path.exists(input_file_path): + print(f"Error: Input file not found at '{input_file_path}'", file=sys.stderr) + sys.exit(1) + + if not os.path.isfile(input_file_path): + print(f"Error: '{input_file_path}' is not a file.", file=sys.stderr) + sys.exit(1) + + output_dir = args.output_dir + if output_dir: + output_dir = os.path.abspath(output_dir) + if not os.path.isdir(output_dir): + print(f"Error: Output directory '{output_dir}' does not exist or is not a directory.", file=sys.stderr) + sys.exit(1) + else: + output_dir = os.path.dirname(input_file_path) + + # Perform conversion + print(f"Starting conversion for {input_file_path}...") + success = converter.convert_video(input_file_path, output_dir) + + if success: + print("Conversion completed successfully.") + else: + print("Conversion failed.", file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..ea51d91 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,59 @@ +import os +import json +import subprocess +import sys + +def get_video_info(file_path): + """Uses ffprobe to get detailed information about a video file.""" + command = [ + 'ffprobe', + '-v', 'error', + '-select_streams', 'v:0', + '-show_entries', 'stream=codec_name,width,height,avg_frame_rate,duration_ts,bit_rate,pix_fmt', + '-of', 'json', + file_path + ] + try: + result = subprocess.run(command, capture_output=True, text=True, check=True) + return json.loads(result.stdout) + except subprocess.CalledProcessError as e: + print(f"Error running ffprobe: {e}", file=sys.stderr) + print(f"Stderr: {e.stderr}", file=sys.stderr) + return None + except FileNotFoundError: + print("Error: ffprobe not found. Please ensure FFmpeg is installed and in your PATH.", file=sys.stderr) + return None + +def get_audio_info(file_path): + """Uses ffprobe to get detailed information about an audio stream in a video file.""" + command = [ + 'ffprobe', + '-v', 'error', + '-select_streams', 'a:0', + '-show_entries', 'stream=codec_name,sample_rate,channels,bit_rate', + '-of', 'json', + file_path + ] + try: + result = subprocess.run(command, capture_output=True, text=True, check=True) + return json.loads(result.stdout) + except subprocess.CalledProcessError as e: + print(f"Error running ffprobe: {e}", file=sys.stderr) + print(f"Stderr: {e.stderr}", file=sys.stderr) + return None + except FileNotFoundError: + print("Error: ffprobe not found. Please ensure FFmpeg is installed and in your PATH.", file=sys.stderr) + return None + +def generate_output_path(input_file_path, output_dir, suffix="_DR_compatible", prefix="recoded_", new_extension=".mov"): + """Generates the output file path based on input, output directory, and naming conventions.""" + base_name = os.path.basename(input_file_path) + name_without_ext = os.path.splitext(base_name)[0] + + final_name = f"{name_without_ext}{suffix}{new_extension}" + + # Apply recoded_ prefix if output_dir is the same as input_file_path's directory + if os.path.abspath(output_dir) == os.path.abspath(os.path.dirname(input_file_path)): + final_name = f"{prefix}{final_name}" + + return os.path.join(output_dir, final_name) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_converter.py b/tests/test_converter.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..e69de29