From 4f7440bdf919af60b20fa0f9dcff172f9fe08ee9 Mon Sep 17 00:00:00 2001 From: Naoshige Tozawa <42102311+Dolphiiiin@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:05:32 +0900 Subject: [PATCH 1/6] first commit 1.0.0 --- README.md | 68 ++++- playblast-ffmpeg.ui | 690 ++++++++++++++++++++++++++++++++++++++++++++ playblast_ffmpeg.py | 352 ++++++++++++++++++++++ 3 files changed, 1108 insertions(+), 2 deletions(-) create mode 100644 playblast-ffmpeg.ui create mode 100644 playblast_ffmpeg.py diff --git a/README.md b/README.md index 64afb4c..dbed6f2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,66 @@ -# playblast-ffmpeg -Playblast and encode with ffmpeg for Maya +# Playblast ffmpeg +Playblast and encode with ffmpeg for Maya. + +## 機能 +- カスタム設定でPlayblast +- ffmpegでPlayblastをエンコード +- エンコード後にPlayblastファイルを自動的に削除 +- エンコード後にファイルとフォルダを開く + +## 環境 +- Maya 2017以降 (Maya 2025で動作確認済み) +- PySide2またはPySide6 +- ffmpeg + +## インストール +1. `playblast-ffmpeg.py`と`playblast-ffmpeg.ui`ファイルをダウンロード +2. 公式サイトから`ffmpeg`実行ファイルをダウンロード: https://ffmpeg.org/download.html +3. ダウンロードしたスクリプトと`ffmpeg`実行ファイルをMayaの`scripts`ディレクトリに配置 +(日本語版Mayaでは`C:\Users\{UserName}\Documents\maya\{Version}\ja_JP\scripts`に配置します) + +## 使い方 +1. Mayaのスクリプトエディタで以下のコードを実行: +```python +import playblast_ffmpeg +playblast_ffmpeg.showUI() +``` +2. プレイブラストオプションを設定 +3. `Export`ボタンをクリックしてビデオをPlayblastしてエンコード + +## ライセンス +MIT License + +--- + +## Features +- Playblast with custom settings +- Encode playblast with ffmpeg +- Automatically delete playblast file after encoding +- Open file and folder after encoding + +## Requirements +- Maya 2017 or later (tested on Maya 2025) +- PySide2 or PySide6 +- ffmpeg + +## Installation +1. Download the `playblast-ffmpeg.py` and `playblast-ffmpeg.ui` files +2. Download the `ffmpeg` executable from the official site: https://ffmpeg.org/download.html +3. Place the downloaded scripts and the `ffmpeg` executable in the Maya `scripts` directory + +## Usage +1. Run the following code in the Maya script editor: +```python +import playblast_ffmpeg +playblast_ffmpeg.showUI() +``` +2. Set the playblast options +3. Click the `Export` button to playblast and encode the video + +## License +MIT License + + +# Japanese + + diff --git a/playblast-ffmpeg.ui b/playblast-ffmpeg.ui new file mode 100644 index 0000000..4b6a1e3 --- /dev/null +++ b/playblast-ffmpeg.ui @@ -0,0 +1,690 @@ + + + playblast_ffmpeg + + + + 0 + 0 + 500 + 670 + + + + MainWindow + + + Qt::ToolButtonIconOnly + + + + + + 10 + 10 + 461 + 321 + + + + プレイブラスト設定 + + + + + 10 + 80 + 441 + 91 + + + + + + + + + 110 + 32 + 231 + 22 + + + + + + + false + + + false + + + false + + + 9999 + + + 1280 + + + + + + + 9999 + + + 720 + + + + + + + + + 50 + 10 + 166 + 22 + + + + + + + 表示サイズ + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + 1 + + + + ウィンドウから + + + + + レンダー設定から + + + + + カスタム + + + + + + + + + + 60 + 60 + 224 + 22 + + + + + + + スケール + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 1.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + フレームパディング + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 10 + + + 1 + + + 4 + + + + + + + + + + 10 + 20 + 441 + 61 + + + + + + + + + 40 + 10 + 61 + 16 + + + + 装飾の表示 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 10 + 16 + 16 + + + + + + + + + + 110 + 29 + 51 + 21 + + + + 100 + + + 5 + + + 50 + + + + + + 80 + 30 + 24 + 16 + + + + 精度 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 270 + 10 + 16 + 16 + + + + + + + + + + 170 + 10 + 91 + 16 + + + + ポリゴンのみ表示 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 10 + 170 + 441 + 141 + + + + + + + + + 20 + 10 + 81 + 16 + + + + 上書きして保存 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 10 + 16 + 16 + + + + + + + true + + + + + + 110 + 90 + 321 + 20 + + + + + + + 20 + 90 + 81 + 16 + + + + 保存先 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 110 + 31 + 23 + + + + ... + + + + + + 20 + 30 + 81 + 16 + + + + 動画を開く + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 30 + 16 + 16 + + + + + + + true + + + + + + 20 + 50 + 81 + 16 + + + + フォルダを開く + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 50 + 16 + 16 + + + + + + + + + + 20 + 70 + 81 + 16 + + + + ファイル名 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 70 + 181 + 20 + + + + + + + 290 + 70 + 50 + 16 + + + + .mp4 + + + + + + + + 10 + 340 + 471 + 261 + + + + Advanced Settings + + + + + 10 + 100 + 451 + 80 + + + + ffmpeg Option + + + + + 10 + 20 + 431 + 51 + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS UI Gothic'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">-y -vcodec libx264 -pix_fmt yuv420p -crf 23 -acodec aac -strict -2 -b:a 384k -movflags faststart </p></body></html> + + + + + + + 10 + 180 + 451 + 71 + + + + ffmpeg path + + + + + 10 + 20 + 161 + 16 + + + + userScriptDir/ffmpeg.exe + + + true + + + + + + 10 + 40 + 71 + 16 + + + + Custome + + + false + + + + + + 80 + 40 + 361 + 20 + + + + + + + + 10 + 20 + 451 + 71 + + + + playblast option + + + + + 90 + 20 + 341 + 20 + + + + + + + 10 + 20 + 71 + 16 + + + + export path + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 50 + 71 + 16 + + + + auto delete + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 90 + 50 + 16 + 16 + + + + + + + true + + + + + + + + 380 + 610 + 91 + 31 + + + + Export + + + + + + + 0 + 0 + 500 + 21 + + + + + 編集 + + + + + + + + 設定のリセット + + + + + + diff --git a/playblast_ffmpeg.py b/playblast_ffmpeg.py new file mode 100644 index 0000000..a5955bf --- /dev/null +++ b/playblast_ffmpeg.py @@ -0,0 +1,352 @@ +# playblast-ffmpeg +# # description: Playblast and encode with ffmpeg for Maya +# # version: 1.0 +# +# Tested on Maya 2025 +# Requires ffmpeg.exe in the userScript directory +# UI file: playblast-ffmpeg.ui +# + +import os +import subprocess +from maya import cmds + +try: + from PySide2 import QtWidgets, QtGui + from PySide2.QtUiTools import QUiLoader +except ImportError: + from PySide6 import QtWidgets, QtGui + from PySide6.QtUiTools import QUiLoader +from maya.app.general.mayaMixin import MayaQWidgetBaseMixin + + +UI_FILE_PATH = cmds.internalVar(userScriptDir=True) + '/playblast-ffmpeg.ui' + +# UI_FILE_PATHの確認 +if not os.path.exists(UI_FILE_PATH): + raise RuntimeError(f"UI file not found: {UI_FILE_PATH}") + +class showUI(MayaQWidgetBaseMixin, QtWidgets.QMainWindow): + def __init__(self, *args, **kwargs): + # 現在開いているウィンドウから、このUIが開いているか確認して、開かれている場合は閉じる + for widget in QtWidgets.QApplication.instance().topLevelWidgets(): + if widget.objectName() == 'Playblast ffmpeg': + widget.close() + + super(showUI, self).__init__(*args, **kwargs) + + # 作ったUIファイルをロードして変数に入れる + self.widget = QUiLoader().load(UI_FILE_PATH) + self.setWindowTitle(self.widget.windowTitle()) + self.setCentralWidget(self.widget) + self.setWindowTitle('Playblast ffmpeg') + self.resize(self.widget.width(), self.widget.height()) + self.show() + + # ウィンドウが開かれるときにoptionVarから値を取得してウィンドウに反映する + self.widget_list = [ + ['decoration_checkBox', 'QCheckBox', 0], + ['polygon_checkBox', 'QCheckBox', 0], + ['percent_spin', 'QSpinBox', 100], + ['size_comboBox', 'QComboBox', 1], + ['customeSize_W', 'QSpinBox', 1280], + ['customeSize_H', 'QSpinBox', 720], + ['scale_spin', 'QDoubleSpinBox', 1.0], + ['padding_spin', 'QSpinBox', 4], + ['overWrite_checkBox', 'QCheckBox', 1], + ['openFile_checkBox', 'QCheckBox', 1], + ['openFolder_checkBox', 'QCheckBox', 0], + ['fileName_lineEdit', 'QLineEdit', ''], + ['savePath_lineEdit', 'QLineEdit', ''], + ['exportPath_lineEdit', 'QLineEdit', 'C:/temp/playblast_temp.avi'], + ['autoDelete_checkBox', 'QCheckBox', 1], + ['ffmpegOption_textEdit', 'QTextEdit', '-y -vcodec libx264 -pix_fmt yuv420p -crf 23 -acodec aac -strict -2 ' + '-b:a 384k -movflags faststart '], + ['ffmpegPath_Default_radioButton', 'QRadioButton', 1], + ['ffmpegPath_Custome_radioButton', 'QRadioButton', 0], + ['ffmpegPath_lineEdit', 'QLineEdit', ''] + ] + + # ウィンドウが閉じられるときに、optionVarに値を保存する + self.reflect_optionVar_to_window(self.widget_list) + + # resetボタンが押されたときに、デフォルト値に戻す + self.widget.reset_action.triggered.connect(self.reset_widgets) + + + # ウィンドウが開かれたときに、size_comboBoxの値をチェック + self.update_custom_size_enabled() + # size_comboBoxの値が変更されたときにスロットをトリガー + self.widget.size_comboBox.currentIndexChanged.connect(self.update_custom_size_enabled) + + # locate_buttonが押されたときに、savePath_lineEditにパスを入れる + self.widget.locate_button.clicked.connect(self.savePath_locate) + + # export_pushButtonが押されたときに、export_playblast関数を実行 + self.widget.export_pushButton.clicked.connect(self.export_playblast) + + + ##### ウィジェットの有効無効を切り替える ##### + + def update_custom_size_enabled(self): + debug("Entering update_custom_size_enabled") + if self.widget.size_comboBox.currentIndex() != 2: + self.widget.customeSize_W.setEnabled(False) + self.widget.customeSize_H.setEnabled(False) + else: + self.widget.customeSize_W.setEnabled(True) + self.widget.customeSize_H.setEnabled(True) + debug("Exiting update_custom_size_enabled") + + # ウィンドウが閉じられるときに、optionVarに値を保存する + def closeEvent(self, event): + debug("Entering closeEvent") + self.save_widget_values_to_optionVar(self.widget_list) + debug("Exiting closeEvent") + event.accept() + + # ウィジェットの値をデフォルト値に戻す + def reset_widgets(self): + debug("Entering reset_widgets") + for widget_name, widget_type, default_value in self.widget_list: + widget = getattr(self.widget, widget_name) + if widget_type == 'QCheckBox' or widget_type == 'QRadioButton': + widget.setChecked(default_value) + elif widget_type in ['QSpinBox', 'QDoubleSpinBox']: + widget.setValue(default_value) + elif widget_type == 'QComboBox': + widget.setCurrentIndex(default_value) + elif widget_type == 'QLineEdit': + widget.setText(default_value) + elif widget_type == 'QTextEdit': + widget.setPlainText(default_value) + else: + debug(f"Unsupported widget type: {widget_type}") + debug("Exiting reset_widgets") + + def save_widget_values_to_optionVar(self, widget_list): + debug("Entering save_widget_values_to_optionVar") + for widget_name, widget_type, _ in widget_list: + widget = getattr(self.widget, widget_name) + if widget_type == 'QCheckBox' or widget_type == 'QRadioButton': + cmds.optionVar(intValue=('pbff_'+widget_name, widget.isChecked())) + elif widget_type in ['QSpinBox', 'QDoubleSpinBox']: + cmds.optionVar(intValue=('pbff_'+widget_name, widget.value())) + elif widget_type == 'QComboBox': + cmds.optionVar(intValue=('pbff_'+widget_name, widget.currentIndex())) + elif widget_type == 'QLineEdit': + cmds.optionVar(stringValue=('pbff_'+widget_name, widget.text())) + elif widget_type == 'QTextEdit': + cmds.optionVar(stringValue=('pbff_'+widget_name, widget.toPlainText())) + else: + debug(f"Unsupported widget type: {widget_type}") + debug("Exiting save_widget_values_to_optionVar") + + def reflect_optionVar_to_window(self, widget_list): + debug("Entering reflect_optionVar_to_window") + for widget_name, widget_type, _ in widget_list: + if cmds.optionVar(exists='pbff_'+widget_name): + value = cmds.optionVar(query='pbff_'+widget_name) + widget = getattr(self.widget, widget_name) + if widget_type == 'QCheckBox': + widget.setChecked(value) + elif widget_type == 'QSpinBox': + widget.setValue(value) + elif widget_type == 'QDoubleSpinBox': + widget.setValue(value) + elif widget_type == 'QLineEdit': + widget.setText(value) + elif widget_type == 'QComboBox': + widget.setCurrentIndex(value) + elif widget_type == 'QRadioButton': + widget.setChecked(value) + else: + debug(f"Unsupported widget type: {widget_type}") + debug("Exiting reflect_optionVar_to_window") + + def savePath_locate(self): + debug("Entering savePath_locate") + # ファイルダイアログを開いて、フォルダを選択する + savePath = QtWidgets.QFileDialog.getExistingDirectory(self, "Select Directory") + if savePath: + self.widget.savePath_lineEdit.setText(savePath) + debug("Exiting savePath_locate") + + + def export_playblast(self): + debug("Entering export_playblast") + # バリテーション + # # fileName_lineEditが空の場合はエラーを出す + if not self.widget.fileName_lineEdit.text(): + QtWidgets.QMessageBox.critical(self, "Error", "ファイル名を入力してください。") + cmds.error("Please input file name.") + return + # # savePath_lineEditが空の場合はエラーを出す + if not self.widget.savePath_lineEdit.text(): + QtWidgets.QMessageBox.critical(self, "Error", "保存先を入力してください。") + cmds.error("Please input save path.") + return + + # # 保存先のフォルダが存在しない場合は、ダイアログで作成するかを確認 + if not os.path.isdir(self.widget.savePath_lineEdit.text()): + reply = QtWidgets.QMessageBox.question(self, 'フォルダ作成確認', f'{self.widget.savePath_lineEdit.text()} は存在しません。作成しますか?', + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.No: + debug("User chose not to create the folder.") + return + os.makedirs(self.widget.savePath_lineEdit.text()) + + # # exportPath_lineEditのフォルダ存在しない場合は、ダイアログで作成するかを確認 + if not os.path.isdir(os.path.dirname(self.widget.exportPath_lineEdit.text())): + reply = QtWidgets.QMessageBox.question(self, 'フォルダ作成確認', f'{os.path.dirname(self.widget.exportPath_lineEdit.text())} は存在しません。作成しますか?', + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.No: + debug("User chose not to create the folder.") + return + os.makedirs(os.path.dirname(self.widget.exportPath_lineEdit.text())) + + # # ffmpegPath_Custome_radioButtonが1で、ffmpegPath_lineEditが空の場合はエラーを出す + if self.widget.ffmpegPath_Custome_radioButton.isChecked() and not self.widget.ffmpegPath_lineEdit.text(): + QtWidgets.QMessageBox.critical(self, "Error", "Please input ffmpeg path.") + cmds.error("Please input ffmpeg path.") + return + + view = False + decoration = self.widget.decoration_checkBox.isChecked() + percent = self.widget.percent_spin.value() + start_time = cmds.playbackOptions(query=True, min=True) + end_time = cmds.playbackOptions(query=True, max=True) + scale = self.widget.scale_spin.value() *100 + frame_padding = self.widget.padding_spin.value() + + # {savePath_lineEdit}/{fileName_lineEdit}.mp4 が存在する場合、上書きするか確認 + savePath = self.widget.savePath_lineEdit.text() + fileNmae = self.widget.fileName_lineEdit.text() + '.mp4' + if os.path.exists(f'{savePath}/{fileNmae}'): + if not self.widget.overWrite_checkBox.isChecked(): + reply = QtWidgets.QMessageBox.question(self, '上書き確認', f'{savePath}/{fileNmae} は既に存在します。上書きしますか?', + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.No: + debug("User chose not to overwrite the file.") + return + + + + # widthHeightの値を取得 + if self.widget.size_comboBox.currentIndex() == 1: + # レンダー設定の解像度を取得 + render_width = cmds.getAttr("defaultResolution.width") + render_height = cmds.getAttr("defaultResolution.height") + width_height = (render_width, render_height) + elif self.widget.size_comboBox.currentIndex() == 2: + width_height = (self.widget.customeSize_W.value(), self.widget.customeSize_H.value()) + else: + debug("Unsupported size_comboBox index.") + return + + playblast_output_path = self.widget.exportPath_lineEdit.text() + + playblast_options = { + 'format': 'avi', + 'sequenceTime': False, + 'viewer': view, + 'showOrnaments': decoration, + 'percent': percent, + 'startTime': start_time, + 'endTime': end_time, + 'framePadding': frame_padding, + 'clearCache': True, + 'forceOverwrite': True, + 'filename': playblast_output_path, + 'widthHeight': width_height, + } + + # ポリゴンのみをビューポートで表示する + if self.widget.polygon_checkBox.isChecked(): + cmds.modelEditor(cmds.playblast(ae=True), edit=True, allObjects=False, polymeshes=True) + + # playblast_output_pathのファイルが存在する場合、削除する + if os.path.exists(playblast_output_path): + try: + os.remove(playblast_output_path) + except PermissionError: + # ファイルが使用中の場合、少し待機して再試行 + time.sleep(1) + try: + os.remove(playblast_output_path) + except PermissionError: + QtWidgets.QMessageBox.critical(self, "Error", f"ファイル {playblast_output_path} を削除できません。他のアプリケーションで開かれている可能性があります。") + cmds.error(f"Cannot delete file: {playblast_output_path}. It may be open in another application.") + return + + # プレイブラスト実行 + cmds.playblast(**playblast_options) + + # プレイブラスト後に元の表示設定に戻す + if self.widget.polygon_checkBox.isChecked(): + cmds.modelEditor(cmds.playblast(ae=True), edit=True, allObjects=True) + + debug("Playblast done. Path: " + playblast_output_path) + + # ffmpegでエンコード + ffmpeg_path = '' + if self.widget.ffmpegPath_Default_radioButton.isChecked(): + # userScript/ffmpeg.exe + ffmpeg_path = cmds.internalVar(userScriptDir=True) + 'ffmpeg.exe' + elif self.widget.ffmpegPath_Custome_radioButton.isChecked(): + ffmpeg_path = self.widget.ffmpegPath_lineEdit.text() + + ffmpeg_option = self.widget.ffmpegOption_textEdit.toPlainText() + + savePath = self.widget.savePath_lineEdit.text() + if not os.path.isdir(savePath): + os.makedirs(savePath) + + fileNmae = self.widget.fileName_lineEdit.text() + '.mp4' + + ffmpeg_command = f'"{ffmpeg_path}" -i "{playblast_output_path}" {ffmpeg_option} "{savePath}/{fileNmae}"' + process = subprocess.Popen(ffmpeg_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) + + # ffmpegの出力を表示 + for line in iter(process.stdout.readline, ''): + if line: + debug(f'ffmpeg output: {line.strip()}') + + process.stdout.close() + process.wait() + + if process.returncode != 0: + error_message = f'ffmpeg error: {process.returncode}' + debug(error_message) + QtWidgets.QMessageBox.critical(self, "Error", error_message) + cmds.error(error_message) + else: + debug(f'ffmpeg done. Path: {savePath}/{fileNmae}') + + # プレイブラスト後のファイルを削除 + if self.widget.autoDelete_checkBox.isChecked(): + os.remove(playblast_output_path) + debug(f'Playblast file deleted: {playblast_output_path}') + + # ファイルを開く + if self.widget.openFile_checkBox.isChecked(): + os.startfile(f'{savePath}/{fileNmae}') + + # フォルダを開く + if self.widget.openFolder_checkBox.isChecked(): + os.startfile(savePath) + + + + debug("Exiting export_playblast") + +def debug(log): + print('[playblast-ffmpeg]: '+log) + +debug('loaded playblast_ffmpeg.py') + +# how to use +# import playblast_ffmpeg +# playblast_ffmpeg.showUI() + +# showUI() From dd5593472f1991aef9810285ccd91135f94fba67 Mon Sep 17 00:00:00 2001 From: Naoshige Tozawa <42102311+Dolphiiiin@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:08:27 +0900 Subject: [PATCH 2/6] Add MIT Licence - Add LICENCE.md - Add link to LICENCE.md on README.md --- LICENCE.md | 7 +++++++ README.md | 6 ++---- 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 LICENCE.md diff --git a/LICENCE.md b/LICENCE.md new file mode 100644 index 0000000..d46f1d8 --- /dev/null +++ b/LICENCE.md @@ -0,0 +1,7 @@ +Copyright 2024 Naoshige Tozawa + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index dbed6f2..68fd737 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ playblast_ffmpeg.showUI() 3. `Export`ボタンをクリックしてビデオをPlayblastしてエンコード ## ライセンス -MIT License +[MIT Licence](LICENCE.md) --- @@ -58,9 +58,7 @@ playblast_ffmpeg.showUI() 3. Click the `Export` button to playblast and encode the video ## License -MIT License +[MIT License](LICENCE.md) -# Japanese - From c88cec2c7e77d7fd5bb281ed7a81283fe6ec9e96 Mon Sep 17 00:00:00 2001 From: Naoshige Tozawa <42102311+Dolphiiiin@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:40:27 +0900 Subject: [PATCH 3/6] =?UTF-8?q?GUI=E3=81=AE=E8=AA=AC=E6=98=8E=E6=96=87?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 68fd737..a16841e 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,25 @@ playblast_ffmpeg.showUI() 2. プレイブラストオプションを設定 3. `Export`ボタンをクリックしてビデオをPlayblastしてエンコード +# パラメーター +| 項目 | 説明 | +| --- | --- | +| `装飾の表示` | ビューポートのヘッドアップディスプレイのような装飾をプレイブラストに表示します | +| `ポリゴンのみ表示` | ポリゴンのみを表示して、プレイブラストをエンコードします | +| `精度` | レンダリング精度を設定します | +| `表示サイズ` | プレイブラストのレンダリング解像度を設定します | +| `スケール` | 解像度をスケーリングします | +| `フレームパディング` | フレームパディングを設定します | +| `上書きして保存` | 有効の時、上書きの確認ダイアログをスキップします | +| `動画を開く` | エンコード後に動画を開きます | +| `フォルダを開く` | エンコード後にエンコード先のフォルダを開きます | +| `ファイル名` | エンコード先のファイル名を設定します | +| `保存先` | エンコード先のフォルダを指定します | +| `export path` | プレイブラストの保存先を指定します | +| `auto delete` | エンコード後にプレイブラストを削除します | +| `ffmpeg Option` | ffmpegのオプションを指定します | +| `ffmpeg Path` | ffmpegのパスを指定します。 (PATHに設定されているffmpegを使用するためには、Customeに`ffmpeg.exe`を設定します) | + ## ライセンス [MIT Licence](LICENCE.md) @@ -45,6 +64,7 @@ playblast_ffmpeg.showUI() ## Installation 1. Download the `playblast-ffmpeg.py` and `playblast-ffmpeg.ui` files +(English ui files are located in the `en` folder) 2. Download the `ffmpeg` executable from the official site: https://ffmpeg.org/download.html 3. Place the downloaded scripts and the `ffmpeg` executable in the Maya `scripts` directory @@ -57,8 +77,24 @@ playblast_ffmpeg.showUI() 2. Set the playblast options 3. Click the `Export` button to playblast and encode the video +# Parameters +| Item | Description | +| --- | --- | +| `Show ornaments` | Encodes playblast with a heads-up display-like decoration in the viewport | +| `Display polygons only` | Encodes playblast with only polygons displayed | +| `Quality` | Sets rendering accuracy | +| `Display size` | Sets the rendering resolution of the playblast | +| `Scale` | Scales the resolution | +| `Frame padding` | Sets frame padding | +| `Save Overwrite` | Skips the overwrite confirmation dialog when enabled | +| `Open video` | Opens the video after encoding | +| `Open folder` | Opens the destination folder after encoding | +| `file name` | Sets the encoded file name | +| `save path` | Specify the destination folder for encoding | +| `export path` | Specify where to save the playblast | +| `auto delete` | Delete playblast after encoding | +| `ffmpeg Option` | Specify ffmpeg options | +| `ffmpeg path` | Specifies the path to ffmpeg. (To use ffmpeg set to PATH, set `ffmpeg.exe` to Custome) | + ## License [MIT License](LICENCE.md) - - - From a260bc87fa0a5a82f958d8a8eaba34f85e7fd3e9 Mon Sep 17 00:00:00 2001 From: Naoshige Tozawa <42102311+Dolphiiiin@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:41:56 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=E8=8B=B1=E8=AA=9E=E7=89=88=E3=81=AEUI?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- en/playblast-ffmpeg.ui | 690 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 690 insertions(+) create mode 100644 en/playblast-ffmpeg.ui diff --git a/en/playblast-ffmpeg.ui b/en/playblast-ffmpeg.ui new file mode 100644 index 0000000..113f216 --- /dev/null +++ b/en/playblast-ffmpeg.ui @@ -0,0 +1,690 @@ + + + playblast_ffmpeg + + + + 0 + 0 + 500 + 670 + + + + MainWindow + + + Qt::ToolButtonIconOnly + + + + + + 10 + 10 + 461 + 321 + + + + Playblast Settings + + + + + 10 + 80 + 441 + 91 + + + + + + + + + 110 + 32 + 231 + 22 + + + + + + + false + + + false + + + false + + + 9999 + + + 1280 + + + + + + + 9999 + + + 720 + + + + + + + + + 40 + 10 + 175 + 22 + + + + + + + Display size + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + 0 + + + + From Window + + + + + From Render Settings + + + + + Cusome + + + + + + + + + + 60 + 60 + 224 + 22 + + + + + + + Scale + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 1.000000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + Frame padding + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 10 + + + 1 + + + 4 + + + + + + + + + + 10 + 20 + 441 + 61 + + + + + + + + + 0 + 10 + 101 + 16 + + + + Show ornaments + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 10 + 16 + 16 + + + + + + + + + + 110 + 29 + 51 + 21 + + + + 100 + + + 5 + + + 50 + + + + + + 53 + 30 + 51 + 20 + + + + Quality + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 310 + 10 + 16 + 16 + + + + + + + + + + 170 + 10 + 131 + 16 + + + + Display polygons only + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 10 + 170 + 441 + 141 + + + + + + + + + 20 + 10 + 81 + 16 + + + + Save Overwrite + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 10 + 16 + 16 + + + + + + + true + + + + + + 110 + 90 + 321 + 20 + + + + + + + 20 + 90 + 81 + 16 + + + + save path + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 110 + 31 + 23 + + + + ... + + + + + + 20 + 30 + 81 + 16 + + + + Open video + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 30 + 16 + 16 + + + + + + + true + + + + + + 20 + 50 + 81 + 16 + + + + Open folder + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 50 + 16 + 16 + + + + + + + + + + 20 + 70 + 81 + 16 + + + + file name + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 110 + 70 + 181 + 20 + + + + + + + 290 + 70 + 50 + 16 + + + + .mp4 + + + + + + + + 10 + 340 + 471 + 261 + + + + Advanced Settings + + + + + 10 + 100 + 451 + 80 + + + + ffmpeg Option + + + + + 10 + 20 + 431 + 51 + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS UI Gothic'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">-y -vcodec libx264 -pix_fmt yuv420p -crf 23 -acodec aac -strict -2 -b:a 384k -movflags faststart </p></body></html> + + + + + + + 10 + 180 + 451 + 71 + + + + ffmpeg path + + + + + 10 + 20 + 161 + 16 + + + + userScriptDir/ffmpeg.exe + + + true + + + + + + 10 + 40 + 71 + 16 + + + + Custome + + + false + + + + + + 80 + 40 + 361 + 20 + + + + + + + + 10 + 20 + 451 + 71 + + + + playblast option + + + + + 90 + 20 + 341 + 20 + + + + + + + 10 + 20 + 71 + 16 + + + + export path + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 10 + 50 + 71 + 16 + + + + auto delete + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 90 + 50 + 16 + 16 + + + + + + + true + + + + + + + + 380 + 610 + 91 + 31 + + + + Export + + + + + + + 0 + 0 + 500 + 21 + + + + + Edit + + + + + + + + Reset Settings + + + + + + From 73ac94dbdd8cb731fc8c3ce9d8bbf843eae1cf1c Mon Sep 17 00:00:00 2001 From: Naoshige Tozawa <42102311+Dolphiiiin@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:59:33 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=E3=83=AD=E3=82=AE=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E3=83=AC=E3=83=99=E3=83=AB=E3=82=92=E8=A8=AD=E5=AE=9A=E3=81=97?= =?UTF-8?q?=E3=80=81=E3=83=AD=E3=82=B0=E3=81=AE=E5=87=BA=E5=8A=9B=E9=87=8F?= =?UTF-8?q?=E3=82=92=E5=89=8A=E6=B8=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ロギングレベルを設定 - info, errorのみを出力するように設定 --- playblast_ffmpeg.py | 65 +++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/playblast_ffmpeg.py b/playblast_ffmpeg.py index a5955bf..75f7ba3 100644 --- a/playblast_ffmpeg.py +++ b/playblast_ffmpeg.py @@ -89,25 +89,25 @@ def __init__(self, *args, **kwargs): ##### ウィジェットの有効無効を切り替える ##### def update_custom_size_enabled(self): - debug("Entering update_custom_size_enabled") + debug("Entering update_custom_size_enabled", 'trace') if self.widget.size_comboBox.currentIndex() != 2: self.widget.customeSize_W.setEnabled(False) self.widget.customeSize_H.setEnabled(False) else: self.widget.customeSize_W.setEnabled(True) self.widget.customeSize_H.setEnabled(True) - debug("Exiting update_custom_size_enabled") + debug("Exiting update_custom_size_enabled", 'trace') # ウィンドウが閉じられるときに、optionVarに値を保存する def closeEvent(self, event): - debug("Entering closeEvent") + debug("Entering closeEvent", 'trace') self.save_widget_values_to_optionVar(self.widget_list) - debug("Exiting closeEvent") + debug("Exiting closeEvent", 'trace') event.accept() # ウィジェットの値をデフォルト値に戻す def reset_widgets(self): - debug("Entering reset_widgets") + debug("Entering reset_widgets", 'trace') for widget_name, widget_type, default_value in self.widget_list: widget = getattr(self.widget, widget_name) if widget_type == 'QCheckBox' or widget_type == 'QRadioButton': @@ -121,11 +121,11 @@ def reset_widgets(self): elif widget_type == 'QTextEdit': widget.setPlainText(default_value) else: - debug(f"Unsupported widget type: {widget_type}") - debug("Exiting reset_widgets") + debug(f"Unsupported widget type: {widget_type} ({widget_name})", 'error') + debug("Exiting reset_widgets", 'trace') def save_widget_values_to_optionVar(self, widget_list): - debug("Entering save_widget_values_to_optionVar") + debug("Entering save_widget_values_to_optionVar", 'trace') for widget_name, widget_type, _ in widget_list: widget = getattr(self.widget, widget_name) if widget_type == 'QCheckBox' or widget_type == 'QRadioButton': @@ -139,11 +139,11 @@ def save_widget_values_to_optionVar(self, widget_list): elif widget_type == 'QTextEdit': cmds.optionVar(stringValue=('pbff_'+widget_name, widget.toPlainText())) else: - debug(f"Unsupported widget type: {widget_type}") - debug("Exiting save_widget_values_to_optionVar") + debug(f"Unsupported widget type: {widget_type} ({widget_name})", 'error') + debug("Exiting save_widget_values_to_optionVar", 'trace') def reflect_optionVar_to_window(self, widget_list): - debug("Entering reflect_optionVar_to_window") + debug("Entering reflect_optionVar_to_window", 'trace') for widget_name, widget_type, _ in widget_list: if cmds.optionVar(exists='pbff_'+widget_name): value = cmds.optionVar(query='pbff_'+widget_name) @@ -161,20 +161,20 @@ def reflect_optionVar_to_window(self, widget_list): elif widget_type == 'QRadioButton': widget.setChecked(value) else: - debug(f"Unsupported widget type: {widget_type}") - debug("Exiting reflect_optionVar_to_window") + debug(f"Unsupported widget type: {widget_type} ({widget_name})", 'error') + debug("Exiting reflect_optionVar_to_window", 'trace') def savePath_locate(self): - debug("Entering savePath_locate") + debug("Entering savePath_locate", 'trace') # ファイルダイアログを開いて、フォルダを選択する savePath = QtWidgets.QFileDialog.getExistingDirectory(self, "Select Directory") if savePath: self.widget.savePath_lineEdit.setText(savePath) - debug("Exiting savePath_locate") + debug("Exiting savePath_locate", 'trace') def export_playblast(self): - debug("Entering export_playblast") + debug("Entering export_playblast", 'trace') # バリテーション # # fileName_lineEditが空の場合はエラーを出す if not self.widget.fileName_lineEdit.text(): @@ -192,7 +192,7 @@ def export_playblast(self): reply = QtWidgets.QMessageBox.question(self, 'フォルダ作成確認', f'{self.widget.savePath_lineEdit.text()} は存在しません。作成しますか?', QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.No: - debug("User chose not to create the folder.") + debug("User chose not to create the folder.", 'info') return os.makedirs(self.widget.savePath_lineEdit.text()) @@ -201,7 +201,7 @@ def export_playblast(self): reply = QtWidgets.QMessageBox.question(self, 'フォルダ作成確認', f'{os.path.dirname(self.widget.exportPath_lineEdit.text())} は存在しません。作成しますか?', QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.No: - debug("User chose not to create the folder.") + debug("User chose not to create the folder.", 'info') return os.makedirs(os.path.dirname(self.widget.exportPath_lineEdit.text())) @@ -227,7 +227,7 @@ def export_playblast(self): reply = QtWidgets.QMessageBox.question(self, '上書き確認', f'{savePath}/{fileNmae} は既に存在します。上書きしますか?', QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.No: - debug("User chose not to overwrite the file.") + debug("User chose not to overwrite the file.", 'info') return @@ -241,7 +241,7 @@ def export_playblast(self): elif self.widget.size_comboBox.currentIndex() == 2: width_height = (self.widget.customeSize_W.value(), self.widget.customeSize_H.value()) else: - debug("Unsupported size_comboBox index.") + debug("Unsupported size_comboBox index.", 'error') return playblast_output_path = self.widget.exportPath_lineEdit.text() @@ -286,7 +286,7 @@ def export_playblast(self): if self.widget.polygon_checkBox.isChecked(): cmds.modelEditor(cmds.playblast(ae=True), edit=True, allObjects=True) - debug("Playblast done. Path: " + playblast_output_path) + debug("Playblast done. Path: " + playblast_output_path, 'info') # ffmpegでエンコード ffmpeg_path = '' @@ -310,23 +310,23 @@ def export_playblast(self): # ffmpegの出力を表示 for line in iter(process.stdout.readline, ''): if line: - debug(f'ffmpeg output: {line.strip()}') + debug(f'ffmpeg output: {line.strip()}', 'debug') process.stdout.close() process.wait() if process.returncode != 0: error_message = f'ffmpeg error: {process.returncode}' - debug(error_message) + debug(error_message, 'error') QtWidgets.QMessageBox.critical(self, "Error", error_message) cmds.error(error_message) else: - debug(f'ffmpeg done. Path: {savePath}/{fileNmae}') + debug(f'ffmpeg done. Path: {savePath}/{fileNmae}', 'info') # プレイブラスト後のファイルを削除 if self.widget.autoDelete_checkBox.isChecked(): os.remove(playblast_output_path) - debug(f'Playblast file deleted: {playblast_output_path}') + debug(f'Playblast file deleted: {playblast_output_path}', 'info') # ファイルを開く if self.widget.openFile_checkBox.isChecked(): @@ -338,12 +338,19 @@ def export_playblast(self): - debug("Exiting export_playblast") + debug("Exiting export_playblast", 'trace') -def debug(log): - print('[playblast-ffmpeg]: '+log) +def debug(log, level): + # if level == 'trace': + # print('[playblast-ffmpeg] [TRACE]: '+log) + # if level == 'debug': + # print('[playblast-ffmpeg] [DEBUG]: '+log) + if level == 'info': + print('[playblast-ffmpeg] [INFO]: '+log) + if level == 'error': + print('[playblast-ffmpeg] [ERROR]: '+log) -debug('loaded playblast_ffmpeg.py') +debug('loaded playblast_ffmpeg.py', 'info') # how to use # import playblast_ffmpeg From b03538c52487cfacaceec771f50a5349fd341859 Mon Sep 17 00:00:00 2001 From: Naoshige Tozawa <42102311+Dolphiiiin@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:59:46 +0900 Subject: [PATCH 6/6] =?UTF-8?q?ffmpeg=20Option=E3=81=AE=E3=82=BB=E3=83=BC?= =?UTF-8?q?=E3=83=96=E3=81=8C=E6=AD=A3=E5=B8=B8=E3=81=AB=E5=8B=95=E4=BD=9C?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playblast_ffmpeg.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/playblast_ffmpeg.py b/playblast_ffmpeg.py index 75f7ba3..b6ed80f 100644 --- a/playblast_ffmpeg.py +++ b/playblast_ffmpeg.py @@ -160,6 +160,8 @@ def reflect_optionVar_to_window(self, widget_list): widget.setCurrentIndex(value) elif widget_type == 'QRadioButton': widget.setChecked(value) + elif widget_type == 'QTextEdit': + widget.setPlainText(value) else: debug(f"Unsupported widget type: {widget_type} ({widget_name})", 'error') debug("Exiting reflect_optionVar_to_window", 'trace')