31 import xml.dom.minidom
as xml
38 from classes
import info, ui_util, settings
39 from classes.app
import get_app
40 from classes.query
import File
41 from classes.logger
import log
47 import simplejson
as json
55 ui_path = os.path.join(info.PATH,
'windows',
'ui',
'export.ui')
60 QDialog.__init__(self)
83 self.buttonBox.addButton(self.
export_button, QDialogButtonBox.AcceptRole)
84 self.buttonBox.addButton(QPushButton(_(
'Cancel')), QDialogButtonBox.RejectRole)
91 self.delayed_fps_timer.setInterval(200)
93 self.delayed_fps_timer.stop()
96 get_app().window.actionPlay_trigger(
None, force=
"pause")
99 get_app().window.timeline_sync.timeline.ClearAllCache()
102 self.lblChannels.setVisible(
False)
103 self.txtChannels.setVisible(
False)
106 openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK =
True
107 openshot.Settings.Instance().HIGH_QUALITY_SCALING =
True
110 width =
get_app().window.timeline_sync.timeline.info.width
111 height =
get_app().window.timeline_sync.timeline.info.height
112 fps =
get_app().window.timeline_sync.timeline.info.fps
113 sample_rate =
get_app().window.timeline_sync.timeline.info.sample_rate
114 channels =
get_app().window.timeline_sync.timeline.info.channels
115 channel_layout =
get_app().window.timeline_sync.timeline.info.channel_layout
121 self.
timeline = openshot.Timeline(width, height, openshot.Fraction(fps.num, fps.den),
122 sample_rate, channels, channel_layout)
124 self.timeline.info.channel_layout =
get_app().window.timeline_sync.timeline.info.channel_layout
125 self.timeline.info.has_audio =
get_app().window.timeline_sync.timeline.info.has_audio
126 self.timeline.info.has_video =
get_app().window.timeline_sync.timeline.info.has_video
127 self.timeline.info.video_length =
get_app().window.timeline_sync.timeline.info.video_length
128 self.timeline.info.duration =
get_app().window.timeline_sync.timeline.info.duration
129 self.timeline.info.sample_rate =
get_app().window.timeline_sync.timeline.info.sample_rate
130 self.timeline.info.channels =
get_app().window.timeline_sync.timeline.info.channels
133 json_timeline = json.dumps(
get_app().project._data)
134 self.timeline.SetJson(json_timeline)
140 recommended_path = recommended_path = os.path.join(info.HOME_PATH)
141 if app.project.current_filepath:
142 recommended_path = os.path.dirname(app.project.current_filepath)
144 export_path =
get_app().project.get([
"export_path"])
145 if os.path.exists(export_path):
147 self.txtExportFolder.setText(export_path)
150 self.txtExportFolder.setText(recommended_path)
153 if not get_app().project.current_filepath:
155 self.txtFileName.setText(_(
"Untitled Project"))
159 parent_path, filename = os.path.split(
get_app().project.current_filepath)
160 filename, ext = os.path.splitext(filename)
161 self.txtFileName.setText(filename.replace(
"_",
" ").replace(
"-",
" ").capitalize())
164 self.txtImageFormat.setText(
"-%05d.png")
167 export_options = [_(
"Video & Audio"), _(
"Video Only"), _(
"Audio Only"), _(
"Image Sequence")]
168 for option
in export_options:
170 self.cboExportTo.addItem(option)
174 for layout
in [(openshot.LAYOUT_MONO, _(
"Mono (1 Channel)")),
175 (openshot.LAYOUT_STEREO, _(
"Stereo (2 Channel)")),
176 (openshot.LAYOUT_SURROUND, _(
"Surround (3 Channel)")),
177 (openshot.LAYOUT_5POINT1, _(
"Surround (5.1 Channel)")),
178 (openshot.LAYOUT_7POINT1, _(
"Surround (7.1 Channel)"))]:
180 self.channel_layout_choices.append(layout[0])
181 self.cboChannelLayout.addItem(layout[1], layout[0])
185 self.cboSimpleProjectType.currentIndexChanged.connect(
188 self.cboSimpleTarget.currentIndexChanged.connect(
190 self.cboSimpleVideoProfile.currentIndexChanged.connect(
192 self.cboSimpleQuality.currentIndexChanged.connect(
194 self.cboChannelLayout.currentIndexChanged.connect(self.
updateChannels)
201 for profile_folder
in [info.USER_PROFILES_PATH, info.PROFILES_PATH]:
202 for file
in os.listdir(profile_folder):
204 profile_path = os.path.join(profile_folder, file)
205 profile = openshot.Profile(profile_path)
208 profile_name =
"%s (%sx%s)" % (profile.info.description, profile.info.width, profile.info.height)
209 self.profile_names.append(profile_name)
213 self.profile_names.sort()
224 if app.project.get([
'profile'])
in profile_name:
234 for preset_path
in [info.EXPORT_PRESETS_PATH, info.USER_PRESETS_PATH]:
235 for file
in os.listdir(preset_path):
236 xmldoc = xml.parse(os.path.join(preset_path, file))
237 type = xmldoc.getElementsByTagName(
"type")
238 presets.append(_(type[0].childNodes[0].data))
243 presets = list(set(presets))
244 for item
in sorted(presets):
245 self.cboSimpleProjectType.addItem(item, item)
246 if item == _(
"All Formats"):
247 selected_type = type_index
251 self.cboSimpleProjectType.setCurrentIndex(selected_type)
264 self.cboChannelLayout.currentIndexChanged.connect(self.
updateFrameRate)
273 self.delayed_fps_timer.stop()
276 fps_double = self.timeline.info.fps.ToDouble()
279 if self.
timeline and fps_double <= 300.0:
280 log.info(
"Valid framerate detected, sending to libopenshot: %s" % fps_double)
281 self.timeline.ApplyMapperToClips()
283 log.warning(
"Invalid framerate detected, not sending it to libopenshot: %s" % fps_double)
288 for profile, path
in self.profile_paths.items():
289 if profile_name
in profile:
295 for profile, path
in self.profile_paths.items():
296 if profile_path == path:
302 percentage_string =
"%4.1f%% " % (( current_frame - start_frame ) / ( end_frame - start_frame ) * 100)
303 self.progressExportVideo.setValue(current_frame)
304 self.progressExportVideo.setFormat(percentage_string)
305 self.setWindowTitle(
"%s %s" % (percentage_string, path))
310 log.info(
"updateChannels")
311 channels = self.txtChannels.value()
312 channel_layout = self.cboChannelLayout.currentData()
314 if channel_layout == openshot.LAYOUT_MONO:
316 elif channel_layout == openshot.LAYOUT_STEREO:
318 elif channel_layout == openshot.LAYOUT_SURROUND:
320 elif channel_layout == openshot.LAYOUT_5POINT1:
322 elif channel_layout == openshot.LAYOUT_7POINT1:
326 self.txtChannels.setValue(channels)
332 self.timeline.info.width = self.txtWidth.value()
333 self.timeline.info.height = self.txtHeight.value()
334 self.timeline.info.fps.num = self.txtFrameRateNum.value()
335 self.timeline.info.fps.den = self.txtFrameRateDen.value()
336 self.timeline.info.sample_rate = self.txtSampleRate.value()
337 self.timeline.info.channels = self.txtChannels.value()
338 self.timeline.info.channel_layout = self.cboChannelLayout.currentData()
341 self.delayed_fps_timer.start()
344 timeline_length = 0.0
345 fps = self.timeline.info.fps.ToFloat()
346 clips = self.timeline.Clips()
348 clip_last_frame = clip.Position() + clip.Duration()
349 if clip_last_frame > timeline_length:
351 timeline_length = clip_last_frame
357 self.txtStartFrame.setValue(1)
361 self.progressExportVideo.setMinimum(self.txtStartFrame.value())
362 self.progressExportVideo.setMaximum(self.txtEndFrame.value())
363 self.progressExportVideo.setValue(self.txtStartFrame.value())
366 current_fps =
get_app().project.get([
"fps"])
367 current_fps_float = float(current_fps[
"num"]) / float(current_fps[
"den"])
368 new_fps_float = float(self.txtFrameRateNum.value()) / float(self.txtFrameRateDen.value())
373 selected_project = widget.itemData(index)
377 self.cboSimpleTarget.clear()
385 for preset_path
in [info.EXPORT_PRESETS_PATH, info.USER_PRESETS_PATH]:
386 for file
in os.listdir(preset_path):
387 xmldoc = xml.parse(os.path.join(preset_path, file))
388 type = xmldoc.getElementsByTagName(
"type")
390 if _(type[0].childNodes[0].data) == selected_project:
391 titles = xmldoc.getElementsByTagName(
"title")
393 project_types.append(_(title.childNodes[0].data))
398 for item
in sorted(project_types):
399 self.cboSimpleTarget.addItem(item, item)
402 if item == _(
"MP4 (h.264)"):
403 selected_preset = preset_index
408 self.cboSimpleTarget.setCurrentIndex(selected_preset)
411 selected_profile_path = widget.itemData(index)
412 log.info(selected_profile_path)
419 profile = openshot.Profile(selected_profile_path)
422 self.txtWidth.setValue(profile.info.width)
423 self.txtHeight.setValue(profile.info.height)
424 self.txtFrameRateDen.setValue(profile.info.fps.den)
425 self.txtFrameRateNum.setValue(profile.info.fps.num)
426 self.txtAspectRatioNum.setValue(profile.info.display_ratio.num)
427 self.txtAspectRatioDen.setValue(profile.info.display_ratio.den)
428 self.txtPixelRatioNum.setValue(profile.info.pixel_ratio.num)
429 self.txtPixelRatioDen.setValue(profile.info.pixel_ratio.den)
432 self.cboInterlaced.clear()
433 self.cboInterlaced.addItem(_(
"Yes"),
"Yes")
434 self.cboInterlaced.addItem(_(
"No"),
"No")
435 if profile.info.interlaced_frame:
436 self.cboInterlaced.setCurrentIndex(0)
438 self.cboInterlaced.setCurrentIndex(1)
441 selected_target = widget.itemData(index)
442 log.info(selected_target)
453 previous_quality = self.cboSimpleQuality.currentIndex()
454 if previous_quality < 0:
455 previous_quality = self.cboSimpleQuality.count() - 1
456 previous_profile = self.cboSimpleVideoProfile.currentIndex()
457 if previous_profile < 0:
459 self.cboSimpleVideoProfile.clear()
460 self.cboSimpleQuality.clear()
465 for preset_path
in [info.EXPORT_PRESETS_PATH, info.USER_PRESETS_PATH]:
466 for file
in os.listdir(preset_path):
467 xmldoc = xml.parse(os.path.join(preset_path, file))
468 title = xmldoc.getElementsByTagName(
"title")
469 if _(title[0].childNodes[0].data) == selected_target:
470 profiles = xmldoc.getElementsByTagName(
"projectprofile")
476 for profile
in profiles:
477 profiles_list.append(_(profile.childNodes[0].data))
482 profiles_list.append(profile_name)
485 videobitrate = xmldoc.getElementsByTagName(
"videobitrate")
486 for rate
in videobitrate:
487 v_l = rate.attributes[
"low"].value
488 v_m = rate.attributes[
"med"].value
489 v_h = rate.attributes[
"high"].value
490 self.
vbr = {_(
"Low"): v_l, _(
"Med"): v_m, _(
"High"): v_h}
493 audiobitrate = xmldoc.getElementsByTagName(
"audiobitrate")
494 for audiorate
in audiobitrate:
495 a_l = audiorate.attributes[
"low"].value
496 a_m = audiorate.attributes[
"med"].value
497 a_h = audiorate.attributes[
"high"].value
498 self.
abr = {_(
"Low"): a_l, _(
"Med"): a_m, _(
"High"): a_h}
501 vf = xmldoc.getElementsByTagName(
"videoformat")
502 self.txtVideoFormat.setText(vf[0].childNodes[0].data)
503 vc = xmldoc.getElementsByTagName(
"videocodec")
504 self.txtVideoCodec.setText(vc[0].childNodes[0].data)
505 sr = xmldoc.getElementsByTagName(
"samplerate")
506 self.txtSampleRate.setValue(int(sr[0].childNodes[0].data))
507 c = xmldoc.getElementsByTagName(
"audiochannels")
508 self.txtChannels.setValue(int(c[0].childNodes[0].data))
509 c = xmldoc.getElementsByTagName(
"audiochannellayout")
512 ac = xmldoc.getElementsByTagName(
"audiocodec")
513 audio_codec_name = ac[0].childNodes[0].data
514 if audio_codec_name ==
"aac":
516 if openshot.FFmpegWriter.IsValidCodec(
"libfaac"):
517 self.txtAudioCodec.setText(
"libfaac")
518 elif openshot.FFmpegWriter.IsValidCodec(
"libvo_aacenc"):
519 self.txtAudioCodec.setText(
"libvo_aacenc")
520 elif openshot.FFmpegWriter.IsValidCodec(
"aac"):
521 self.txtAudioCodec.setText(
"aac")
524 self.txtAudioCodec.setText(
"ac3")
527 self.txtAudioCodec.setText(audio_codec_name)
531 if layout == int(c[0].childNodes[0].data):
532 self.cboChannelLayout.setCurrentIndex(layout_index)
537 for item
in sorted(profiles_list):
542 self.cboSimpleVideoProfile.setCurrentIndex(previous_profile)
547 self.cboSimpleQuality.addItem(_(
"Low"),
"Low")
549 self.cboSimpleQuality.addItem(_(
"Med"),
"Med")
551 self.cboSimpleQuality.addItem(_(
"High"),
"High")
554 if previous_quality <= self.cboSimpleQuality.count() - 1:
555 self.cboSimpleQuality.setCurrentIndex(previous_quality)
557 self.cboSimpleQuality.setCurrentIndex(self.cboSimpleQuality.count() - 1)
560 selected_profile_path = widget.itemData(index)
561 log.info(selected_profile_path)
575 self.cboProfile.setCurrentIndex(profile_index)
582 selected_quality = widget.itemData(index)
583 log.info(selected_quality)
591 self.txtVideoBitRate.setText(_(self.
vbr[_(selected_quality)]))
592 self.txtAudioBitrate.setText(_(self.
abr[_(selected_quality)]))
595 log.info(
"btnBrowse_clicked")
602 file_path = QFileDialog.getExistingDirectory(self, _(
"Choose a Folder..."), self.txtExportFolder.text())
603 if os.path.exists(file_path):
604 self.txtExportFolder.setText(file_path)
607 get_app().updates.update([
"export_path"], file_path)
613 s = BitRateString.lower().split(
" ")
619 raw_number_string = s[0]
620 raw_measurement = s[1]
623 raw_number = locale.atof(raw_number_string)
625 if "kb" in raw_measurement:
627 bit_rate_bytes = raw_number * 1000.0
629 elif "mb" in raw_measurement:
631 bit_rate_bytes = raw_number * 1000.0 * 1000.0
633 elif "crf" in raw_measurement:
639 bit_rate_bytes = raw_number
645 return str(int(bit_rate_bytes))
656 self.txtFileName.setEnabled(
False)
657 self.txtExportFolder.setEnabled(
False)
658 self.tabWidget.setEnabled(
False)
659 self.export_button.setEnabled(
False)
664 export_type = self.cboExportTo.currentText()
667 if export_type != _(
"Image Sequence"):
668 file_name_with_ext =
"%s.%s" % (self.txtFileName.text().strip(), self.txtVideoFormat.text().strip())
670 file_name_with_ext =
"%s%s" % (self.txtFileName.text().strip(), self.txtImageFormat.text().strip())
671 export_file_path = os.path.join(self.txtExportFolder.text().strip(), file_name_with_ext)
672 log.info(export_file_path)
677 file = File.get(path=export_file_path)
679 ret = QMessageBox.question(self, _(
"Export Video"), _(
"%s is an input file.\nPlease choose a different name.") % file_name_with_ext,
681 self.txtFileName.setEnabled(
True)
682 self.txtExportFolder.setEnabled(
True)
683 self.tabWidget.setEnabled(
True)
684 self.export_button.setEnabled(
True)
689 if os.path.exists(export_file_path)
and export_type
in [_(
"Video & Audio"), _(
"Video Only"), _(
"Audio Only")]:
691 ret = QMessageBox.question(self, _(
"Export Video"), _(
"%s already exists.\nDo you want to replace it?") % file_name_with_ext,
692 QMessageBox.No | QMessageBox.Yes)
693 if ret == QMessageBox.No:
696 self.txtFileName.setEnabled(
True)
697 self.txtExportFolder.setEnabled(
True)
698 self.tabWidget.setEnabled(
True)
699 self.export_button.setEnabled(
True)
704 video_settings = {
"vformat": self.txtVideoFormat.text(),
705 "vcodec": self.txtVideoCodec.text(),
706 "fps": {
"num" : self.txtFrameRateNum.value(),
"den": self.txtFrameRateDen.value()},
707 "width": self.txtWidth.value(),
708 "height": self.txtHeight.value(),
709 "pixel_ratio": {
"num": self.txtPixelRatioNum.value(),
"den": self.txtPixelRatioDen.value()},
711 "start_frame": self.txtStartFrame.value(),
712 "end_frame": self.txtEndFrame.value() + 1
715 audio_settings = {
"acodec": self.txtAudioCodec.text(),
716 "sample_rate": self.txtSampleRate.value(),
717 "channels": self.txtChannels.value(),
718 "channel_layout": self.cboChannelLayout.currentData(),
723 if export_type == _(
"Image Sequence"):
724 image_ext = os.path.splitext(self.txtImageFormat.text().strip())[1].replace(
".",
"")
725 video_settings[
"vformat"] = image_ext
726 if image_ext
in [
"jpg",
"jpeg"]:
727 video_settings[
"vcodec"] =
"mjpeg"
729 video_settings[
"vcodec"] = image_ext
732 self.timeline.SetMaxSize(video_settings.get(
"width"), video_settings.get(
"height"))
735 export_cache_object = openshot.CacheMemory(250)
736 self.timeline.SetCache(export_cache_object)
744 json_timeline = json.dumps(
get_app().project._data)
745 self.timeline.SetJson(json_timeline)
752 w = openshot.FFmpegWriter(export_file_path)
755 if export_type
in [_(
"Video & Audio"), _(
"Video Only"), _(
"Image Sequence")]:
756 w.SetVideoOptions(
True,
757 video_settings.get(
"vcodec"),
758 openshot.Fraction(video_settings.get(
"fps").get(
"num"),
759 video_settings.get(
"fps").get(
"den")),
760 video_settings.get(
"width"),
761 video_settings.get(
"height"),
762 openshot.Fraction(video_settings.get(
"pixel_ratio").get(
"num"),
763 video_settings.get(
"pixel_ratio").get(
"den")),
766 video_settings.get(
"video_bitrate"))
769 if export_type
in [_(
"Video & Audio"), _(
"Audio Only")]:
770 w.SetAudioOptions(
True,
771 audio_settings.get(
"acodec"),
772 audio_settings.get(
"sample_rate"),
773 audio_settings.get(
"channels"),
774 audio_settings.get(
"channel_layout"),
775 audio_settings.get(
"audio_bitrate"))
784 if "crf" in self.txtVideoBitRate.text():
785 w.SetOption(openshot.VIDEO_STREAM,
"crf", str(int(video_settings.get(
"video_bitrate"))) )
791 export_file_path =
""
792 get_app().window.ExportStarted.emit(export_file_path, video_settings.get(
"start_frame"), video_settings.get(
"end_frame"))
794 progressstep = max(1 , round(( video_settings.get(
"end_frame") - video_settings.get(
"start_frame") ) / 1000))
795 start_time_export = time.time()
796 start_frame_export = video_settings.get(
"start_frame")
797 end_frame_export = video_settings.get(
"end_frame")
799 for frame
in range(video_settings.get(
"start_frame"), video_settings.get(
"end_frame")):
801 if (frame % progressstep) == 0:
802 end_time_export = time.time()
803 if ((( frame - start_frame_export ) != 0) & (( end_time_export - start_time_export ) != 0)):
804 seconds_left = round(( start_time_export - end_time_export )*( frame - end_frame_export )/( frame - start_frame_export ))
805 fps_encode = ((frame - start_frame_export)/(end_time_export-start_time_export))
806 export_file_path = _(
"%(hours)d:%(minutes)02d:%(seconds)02d Remaining (%(fps)5.2f FPS)") % {
'hours' : seconds_left / 3600,
807 'minutes': (seconds_left / 60) % 60,
808 'seconds': seconds_left % 60,
810 get_app().window.ExportFrame.emit(export_file_path, video_settings.get(
"start_frame"), video_settings.get(
"end_frame"), frame)
813 QCoreApplication.processEvents()
816 w.WriteFrame(self.timeline.GetFrame(frame))
826 except Exception
as e:
829 error_type_str = str(e)
830 log.info(
"Error type string: %s" % error_type_str)
832 if "InvalidChannels" in error_type_str:
833 log.info(
"Error setting invalid # of channels (%s)" % (audio_settings.get(
"channels")))
834 track_metric_error(
"invalid-channels-%s-%s-%s-%s" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec"), audio_settings.get(
"channels")))
836 elif "InvalidSampleRate" in error_type_str:
837 log.info(
"Error setting invalid sample rate (%s)" % (audio_settings.get(
"sample_rate")))
838 track_metric_error(
"invalid-sample-rate-%s-%s-%s-%s" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec"), audio_settings.get(
"sample_rate")))
840 elif "InvalidFormat" in error_type_str:
841 log.info(
"Error setting invalid format (%s)" % (video_settings.get(
"vformat")))
844 elif "InvalidCodec" in error_type_str:
845 log.info(
"Error setting invalid codec (%s/%s/%s)" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec")))
846 track_metric_error(
"invalid-codec-%s-%s-%s" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec")))
848 elif "ErrorEncodingVideo" in error_type_str:
849 log.info(
"Error encoding video frame (%s/%s/%s)" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec")))
850 track_metric_error(
"video-encode-%s-%s-%s" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec")))
853 friendly_error = error_type_str.split(
"> ")[0].replace(
"<",
"")
858 msg.setWindowTitle(_(
"Export Error"))
859 msg.setText(_(
"Sorry, there was an error exporting your video: \n%s") % friendly_error)
863 get_app().window.ExportEnded.emit(export_file_path)
866 self.timeline.Close()
869 self.timeline.ClearAllCache()
872 if self.s.get(
"omp_threads_enabled"):
873 openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK =
False
875 openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK =
True
878 openshot.Settings.Instance().HIGH_QUALITY_SCALING =
False
885 super(Export, self).
accept()
889 if self.s.get(
"omp_threads_enabled"):
890 openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK =
False
892 openshot.Settings.Instance().WAIT_FOR_VIDEO_PROCESSING_TASK =
True
895 openshot.Settings.Instance().HIGH_QUALITY_SCALING =
False
903 super(Export, self).
reject()
def cboSimpleQuality_index_changed
def updateFrameRate
Callback for changing the frame rate.
def track_metric_screen
Track a GUI screen being shown.
def get_app
Returns the current QApplication instance of OpenShot.
def delayed_fps_callback
Callback for fps/profile changed event timer (to delay the timeline mapping so we don't spam libopens...
def cboSimpleProjectType_index_changed
def track_metric_error
Track an error has occurred.
def cboSimpleVideoProfile_index_changed
def getProfileName
Get the profile name that matches the name.
def load_ui
Load a Qt *.ui file, and also load an XML parsed version.
def cboProfile_index_changed
def updateProgressBar
Update progress bar during exporting.
def updateChannels
Update the # of channels to match the channel layout.
def getProfilePath
Get the profile path that matches the name.
def cboSimpleTarget_index_changed
def init_ui
Initialize all child widgets and action of a window or dialog.
def populateAllProfiles
Populate the full list of profiles.
def get_settings
Get the current QApplication's settings instance.
def accept
Start exporting video.