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 width =
get_app().window.timeline_sync.timeline.info.width
107 height =
get_app().window.timeline_sync.timeline.info.height
108 fps =
get_app().window.timeline_sync.timeline.info.fps
109 sample_rate =
get_app().window.timeline_sync.timeline.info.sample_rate
110 channels =
get_app().window.timeline_sync.timeline.info.channels
111 channel_layout =
get_app().window.timeline_sync.timeline.info.channel_layout
114 self.
timeline = openshot.Timeline(width, height, openshot.Fraction(fps.num, fps.den),
115 sample_rate, channels, channel_layout)
117 self.timeline.info.channel_layout =
get_app().window.timeline_sync.timeline.info.channel_layout
118 self.timeline.info.has_audio =
get_app().window.timeline_sync.timeline.info.has_audio
119 self.timeline.info.has_video =
get_app().window.timeline_sync.timeline.info.has_video
120 self.timeline.info.video_length =
get_app().window.timeline_sync.timeline.info.video_length
121 self.timeline.info.duration =
get_app().window.timeline_sync.timeline.info.duration
122 self.timeline.info.sample_rate =
get_app().window.timeline_sync.timeline.info.sample_rate
123 self.timeline.info.channels =
get_app().window.timeline_sync.timeline.info.channels
126 json_timeline = json.dumps(
get_app().project._data)
127 self.timeline.SetJson(json_timeline)
133 recommended_path = recommended_path = os.path.join(info.HOME_PATH)
134 if app.project.current_filepath:
135 recommended_path = os.path.dirname(app.project.current_filepath)
137 export_path =
get_app().project.get([
"export_path"])
138 if os.path.exists(export_path):
140 self.txtExportFolder.setText(export_path)
143 self.txtExportFolder.setText(recommended_path)
146 if not get_app().project.current_filepath:
148 self.txtFileName.setText(_(
"Untitled Project"))
152 parent_path, filename = os.path.split(
get_app().project.current_filepath)
153 filename, ext = os.path.splitext(filename)
154 self.txtFileName.setText(filename.replace(
"_",
" ").replace(
"-",
" ").capitalize())
157 self.txtImageFormat.setText(
"-%05d.png")
160 export_options = [_(
"Video & Audio"), _(
"Video Only"), _(
"Audio Only"), _(
"Image Sequence")]
161 for option
in export_options:
163 self.cboExportTo.addItem(option)
167 for layout
in [(openshot.LAYOUT_MONO, _(
"Mono (1 Channel)")),
168 (openshot.LAYOUT_STEREO, _(
"Stereo (2 Channel)")),
169 (openshot.LAYOUT_SURROUND, _(
"Surround (3 Channel)")),
170 (openshot.LAYOUT_5POINT1, _(
"Surround (5.1 Channel)")),
171 (openshot.LAYOUT_7POINT1, _(
"Surround (7.1 Channel)"))]:
173 self.channel_layout_choices.append(layout[0])
174 self.cboChannelLayout.addItem(layout[1], layout[0])
178 self.cboSimpleProjectType.currentIndexChanged.connect(
181 self.cboSimpleTarget.currentIndexChanged.connect(
183 self.cboSimpleVideoProfile.currentIndexChanged.connect(
185 self.cboSimpleQuality.currentIndexChanged.connect(
187 self.cboChannelLayout.currentIndexChanged.connect(self.
updateChannels)
194 for profile_folder
in [info.USER_PROFILES_PATH, info.PROFILES_PATH]:
195 for file
in os.listdir(profile_folder):
197 profile_path = os.path.join(profile_folder, file)
198 profile = openshot.Profile(profile_path)
201 profile_name =
"%s (%sx%s)" % (profile.info.description, profile.info.width, profile.info.height)
202 self.profile_names.append(profile_name)
206 self.profile_names.sort()
217 if app.project.get([
'profile'])
in profile_name:
227 for file
in os.listdir(info.EXPORT_PRESETS_DIR):
228 xmldoc = xml.parse(os.path.join(info.EXPORT_PRESETS_DIR, file))
229 type = xmldoc.getElementsByTagName(
"type")
230 presets.append(_(type[0].childNodes[0].data))
235 presets = list(set(presets))
236 for item
in sorted(presets):
237 self.cboSimpleProjectType.addItem(item, item)
238 if item == _(
"All Formats"):
239 selected_type = type_index
243 self.cboSimpleProjectType.setCurrentIndex(selected_type)
256 self.cboChannelLayout.currentIndexChanged.connect(self.
updateFrameRate)
265 self.delayed_fps_timer.stop()
268 fps_double = self.timeline.info.fps.ToDouble()
271 if self.
timeline and fps_double <= 300.0:
272 log.info(
"Valid framerate detected, sending to libopenshot: %s" % fps_double)
273 self.timeline.ApplyMapperToClips()
275 log.warning(
"Invalid framerate detected, not sending it to libopenshot: %s" % fps_double)
280 for profile, path
in self.profile_paths.items():
281 if profile_name
in profile:
287 for profile, path
in self.profile_paths.items():
288 if profile_path == path:
294 percentage_string =
"%4.1f%% " % (( current_frame - start_frame ) / ( end_frame - start_frame ) * 100)
295 self.progressExportVideo.setValue(current_frame)
296 self.progressExportVideo.setFormat(percentage_string)
297 self.setWindowTitle(
"%s %s" % (percentage_string, path))
302 log.info(
"updateChannels")
303 channels = self.txtChannels.value()
304 channel_layout = self.cboChannelLayout.currentData()
306 if channel_layout == openshot.LAYOUT_MONO:
308 elif channel_layout == openshot.LAYOUT_STEREO:
310 elif channel_layout == openshot.LAYOUT_SURROUND:
312 elif channel_layout == openshot.LAYOUT_5POINT1:
314 elif channel_layout == openshot.LAYOUT_7POINT1:
318 self.txtChannels.setValue(channels)
324 self.timeline.info.width = self.txtWidth.value()
325 self.timeline.info.height = self.txtHeight.value()
326 self.timeline.info.fps.num = self.txtFrameRateNum.value()
327 self.timeline.info.fps.den = self.txtFrameRateDen.value()
328 self.timeline.info.sample_rate = self.txtSampleRate.value()
329 self.timeline.info.channels = self.txtChannels.value()
330 self.timeline.info.channel_layout = self.cboChannelLayout.currentData()
333 self.delayed_fps_timer.start()
336 timeline_length = 0.0
337 fps = self.timeline.info.fps.ToFloat()
338 clips = self.timeline.Clips()
340 clip_last_frame = clip.Position() + clip.Duration()
341 if clip_last_frame > timeline_length:
343 timeline_length = clip_last_frame
349 self.txtStartFrame.setValue(1)
353 self.progressExportVideo.setMinimum(self.txtStartFrame.value())
354 self.progressExportVideo.setMaximum(self.txtEndFrame.value())
355 self.progressExportVideo.setValue(self.txtStartFrame.value())
358 selected_project = widget.itemData(index)
362 self.cboSimpleTarget.clear()
370 for file
in os.listdir(info.EXPORT_PRESETS_DIR):
371 xmldoc = xml.parse(os.path.join(info.EXPORT_PRESETS_DIR, file))
372 type = xmldoc.getElementsByTagName(
"type")
374 if _(type[0].childNodes[0].data) == selected_project:
375 titles = xmldoc.getElementsByTagName(
"title")
377 project_types.append(_(title.childNodes[0].data))
382 for item
in sorted(project_types):
383 self.cboSimpleTarget.addItem(item, item)
386 if item == _(
"MP4 (h.264)"):
387 selected_preset = preset_index
392 self.cboSimpleTarget.setCurrentIndex(selected_preset)
395 selected_profile_path = widget.itemData(index)
396 log.info(selected_profile_path)
403 profile = openshot.Profile(selected_profile_path)
406 self.txtWidth.setValue(profile.info.width)
407 self.txtHeight.setValue(profile.info.height)
408 self.txtFrameRateDen.setValue(profile.info.fps.den)
409 self.txtFrameRateNum.setValue(profile.info.fps.num)
410 self.txtAspectRatioNum.setValue(profile.info.display_ratio.num)
411 self.txtAspectRatioDen.setValue(profile.info.display_ratio.den)
412 self.txtPixelRatioNum.setValue(profile.info.pixel_ratio.num)
413 self.txtPixelRatioDen.setValue(profile.info.pixel_ratio.den)
416 self.cboInterlaced.clear()
417 self.cboInterlaced.addItem(_(
"Yes"),
"Yes")
418 self.cboInterlaced.addItem(_(
"No"),
"No")
419 if profile.info.interlaced_frame:
420 self.cboInterlaced.setCurrentIndex(0)
422 self.cboInterlaced.setCurrentIndex(1)
425 selected_target = widget.itemData(index)
426 log.info(selected_target)
437 previous_quality = self.cboSimpleQuality.currentIndex()
438 if previous_quality < 0:
439 previous_quality = self.cboSimpleQuality.count() - 1
440 previous_profile = self.cboSimpleVideoProfile.currentIndex()
441 if previous_profile < 0:
443 self.cboSimpleVideoProfile.clear()
444 self.cboSimpleQuality.clear()
449 for file
in os.listdir(info.EXPORT_PRESETS_DIR):
450 xmldoc = xml.parse(os.path.join(info.EXPORT_PRESETS_DIR, file))
451 title = xmldoc.getElementsByTagName(
"title")
452 if _(title[0].childNodes[0].data) == selected_target:
453 profiles = xmldoc.getElementsByTagName(
"projectprofile")
459 for profile
in profiles:
460 profiles_list.append(_(profile.childNodes[0].data))
465 profiles_list.append(profile_name)
468 videobitrate = xmldoc.getElementsByTagName(
"videobitrate")
469 for rate
in videobitrate:
470 v_l = rate.attributes[
"low"].value
471 v_m = rate.attributes[
"med"].value
472 v_h = rate.attributes[
"high"].value
473 self.
vbr = {_(
"Low"): v_l, _(
"Med"): v_m, _(
"High"): v_h}
476 audiobitrate = xmldoc.getElementsByTagName(
"audiobitrate")
477 for audiorate
in audiobitrate:
478 a_l = audiorate.attributes[
"low"].value
479 a_m = audiorate.attributes[
"med"].value
480 a_h = audiorate.attributes[
"high"].value
481 self.
abr = {_(
"Low"): a_l, _(
"Med"): a_m, _(
"High"): a_h}
484 vf = xmldoc.getElementsByTagName(
"videoformat")
485 self.txtVideoFormat.setText(vf[0].childNodes[0].data)
486 vc = xmldoc.getElementsByTagName(
"videocodec")
487 self.txtVideoCodec.setText(vc[0].childNodes[0].data)
488 sr = xmldoc.getElementsByTagName(
"samplerate")
489 self.txtSampleRate.setValue(int(sr[0].childNodes[0].data))
490 c = xmldoc.getElementsByTagName(
"audiochannels")
491 self.txtChannels.setValue(int(c[0].childNodes[0].data))
492 c = xmldoc.getElementsByTagName(
"audiochannellayout")
495 ac = xmldoc.getElementsByTagName(
"audiocodec")
496 audio_codec_name = ac[0].childNodes[0].data
497 if audio_codec_name ==
"aac":
499 if openshot.FFmpegWriter.IsValidCodec(
"libfaac"):
500 self.txtAudioCodec.setText(
"libfaac")
501 elif openshot.FFmpegWriter.IsValidCodec(
"libvo_aacenc"):
502 self.txtAudioCodec.setText(
"libvo_aacenc")
503 elif openshot.FFmpegWriter.IsValidCodec(
"aac"):
504 self.txtAudioCodec.setText(
"aac")
507 self.txtAudioCodec.setText(
"ac3")
510 self.txtAudioCodec.setText(audio_codec_name)
514 if layout == int(c[0].childNodes[0].data):
515 self.cboChannelLayout.setCurrentIndex(layout_index)
520 for item
in sorted(profiles_list):
525 self.cboSimpleVideoProfile.setCurrentIndex(previous_profile)
530 self.cboSimpleQuality.addItem(_(
"Low"),
"Low")
532 self.cboSimpleQuality.addItem(_(
"Med"),
"Med")
534 self.cboSimpleQuality.addItem(_(
"High"),
"High")
537 self.cboSimpleQuality.setCurrentIndex(previous_quality)
540 selected_profile_path = widget.itemData(index)
541 log.info(selected_profile_path)
555 self.cboProfile.setCurrentIndex(profile_index)
562 selected_quality = widget.itemData(index)
563 log.info(selected_quality)
571 self.txtVideoBitRate.setText(_(self.
vbr[_(selected_quality)]))
572 self.txtAudioBitrate.setText(_(self.
abr[_(selected_quality)]))
575 log.info(
"btnBrowse_clicked")
582 file_path = QFileDialog.getExistingDirectory(self, _(
"Choose a Folder..."), self.txtExportFolder.text())
583 if os.path.exists(file_path):
584 self.txtExportFolder.setText(file_path)
587 get_app().updates.update([
"export_path"], file_path)
593 s = BitRateString.lower().split(
" ")
599 raw_number_string = s[0]
600 raw_measurement = s[1]
603 raw_number = locale.atof(raw_number_string)
605 if "kb" in raw_measurement:
607 bit_rate_bytes = raw_number * 1000.0
609 elif "mb" in raw_measurement:
611 bit_rate_bytes = raw_number * 1000.0 * 1000.0
617 return str(int(bit_rate_bytes))
628 self.txtFileName.setEnabled(
False)
629 self.txtExportFolder.setEnabled(
False)
630 self.tabWidget.setEnabled(
False)
631 self.export_button.setEnabled(
False)
636 export_type = self.cboExportTo.currentText()
639 if export_type != _(
"Image Sequence"):
640 file_name_with_ext =
"%s.%s" % (self.txtFileName.text().strip(), self.txtVideoFormat.text().strip())
642 file_name_with_ext =
"%s%s" % (self.txtFileName.text().strip(), self.txtImageFormat.text().strip())
643 export_file_path = os.path.join(self.txtExportFolder.text().strip(), file_name_with_ext)
644 log.info(export_file_path)
649 file = File.get(path=export_file_path)
651 ret = QMessageBox.question(self, _(
"Export Video"), _(
"%s is an input file.\nPlease choose a different name.") % file_name_with_ext,
653 self.txtFileName.setEnabled(
True)
654 self.txtExportFolder.setEnabled(
True)
655 self.tabWidget.setEnabled(
True)
656 self.export_button.setEnabled(
True)
661 if os.path.exists(export_file_path)
and export_type
in [_(
"Video & Audio"), _(
"Video Only"), _(
"Audio Only")]:
663 ret = QMessageBox.question(self, _(
"Export Video"), _(
"%s already exists.\nDo you want to replace it?") % file_name_with_ext,
664 QMessageBox.No | QMessageBox.Yes)
665 if ret == QMessageBox.No:
668 self.txtFileName.setEnabled(
True)
669 self.txtExportFolder.setEnabled(
True)
670 self.tabWidget.setEnabled(
True)
671 self.export_button.setEnabled(
True)
676 video_settings = {
"vformat": self.txtVideoFormat.text(),
677 "vcodec": self.txtVideoCodec.text(),
678 "fps": {
"num" : self.txtFrameRateNum.value(),
"den": self.txtFrameRateDen.value()},
679 "width": self.txtWidth.value(),
680 "height": self.txtHeight.value(),
681 "pixel_ratio": {
"num": self.txtPixelRatioNum.value(),
"den": self.txtPixelRatioDen.value()},
683 "start_frame": self.txtStartFrame.value(),
684 "end_frame": self.txtEndFrame.value() + 1
687 audio_settings = {
"acodec": self.txtAudioCodec.text(),
688 "sample_rate": self.txtSampleRate.value(),
689 "channels": self.txtChannels.value(),
690 "channel_layout": self.cboChannelLayout.currentData(),
695 if export_type == _(
"Image Sequence"):
696 image_ext = os.path.splitext(self.txtImageFormat.text().strip())[1].replace(
".",
"")
697 video_settings[
"vformat"] = image_ext
698 if image_ext
in [
"jpg",
"jpeg"]:
699 video_settings[
"vcodec"] =
"mjpeg"
701 video_settings[
"vcodec"] = image_ext
704 self.timeline.SetMaxSize(video_settings.get(
"width"), video_settings.get(
"height"))
707 export_cache_object = openshot.CacheMemory(250)
708 self.timeline.SetCache(export_cache_object)
712 w = openshot.FFmpegWriter(export_file_path)
715 if export_type
in [_(
"Video & Audio"), _(
"Video Only"), _(
"Image Sequence")]:
716 w.SetVideoOptions(
True,
717 video_settings.get(
"vcodec"),
718 openshot.Fraction(video_settings.get(
"fps").get(
"num"),
719 video_settings.get(
"fps").get(
"den")),
720 video_settings.get(
"width"),
721 video_settings.get(
"height"),
722 openshot.Fraction(video_settings.get(
"pixel_ratio").get(
"num"),
723 video_settings.get(
"pixel_ratio").get(
"den")),
726 video_settings.get(
"video_bitrate"))
729 if export_type
in [_(
"Video & Audio"), _(
"Audio Only")]:
730 w.SetAudioOptions(
True,
731 audio_settings.get(
"acodec"),
732 audio_settings.get(
"sample_rate"),
733 audio_settings.get(
"channels"),
734 audio_settings.get(
"channel_layout"),
735 audio_settings.get(
"audio_bitrate"))
741 export_file_path =
""
742 get_app().window.ExportStarted.emit(export_file_path, video_settings.get(
"start_frame"), video_settings.get(
"end_frame"))
744 progressstep = max(1 , round(( video_settings.get(
"end_frame") - video_settings.get(
"start_frame") ) / 1000))
745 start_time_export = time.time()
746 start_frame_export = video_settings.get(
"start_frame")
747 end_frame_export = video_settings.get(
"end_frame")
749 for frame
in range(video_settings.get(
"start_frame"), video_settings.get(
"end_frame")):
751 if (frame % progressstep) == 0:
752 end_time_export = time.time()
753 if ((( frame - start_frame_export ) != 0) & (( end_time_export - start_time_export ) != 0)):
754 seconds_left = round(( start_time_export - end_time_export )*( frame - end_frame_export )/( frame - start_frame_export ))
755 fps_encode = ((frame - start_frame_export)/(end_time_export-start_time_export))
756 export_file_path = _(
"%(hours)d:%(minutes)02d:%(seconds)02d Remaining (%(fps)5.2f FPS)") % {
'hours' : seconds_left / 3600,
757 'minutes': (seconds_left / 60) % 60,
758 'seconds': seconds_left % 60,
760 get_app().window.ExportFrame.emit(export_file_path, video_settings.get(
"start_frame"), video_settings.get(
"end_frame"), frame)
763 QCoreApplication.processEvents()
766 w.WriteFrame(self.timeline.GetFrame(frame))
776 except Exception
as e:
779 error_type_str = str(e)
780 log.info(
"Error type string: %s" % error_type_str)
782 if "InvalidChannels" in error_type_str:
783 log.info(
"Error setting invalid # of channels (%s)" % (audio_settings.get(
"channels")))
784 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")))
786 elif "InvalidSampleRate" in error_type_str:
787 log.info(
"Error setting invalid sample rate (%s)" % (audio_settings.get(
"sample_rate")))
788 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")))
790 elif "InvalidFormat" in error_type_str:
791 log.info(
"Error setting invalid format (%s)" % (video_settings.get(
"vformat")))
794 elif "InvalidCodec" in error_type_str:
795 log.info(
"Error setting invalid codec (%s/%s/%s)" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec")))
796 track_metric_error(
"invalid-codec-%s-%s-%s" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec")))
798 elif "ErrorEncodingVideo" in error_type_str:
799 log.info(
"Error encoding video frame (%s/%s/%s)" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec")))
800 track_metric_error(
"video-encode-%s-%s-%s" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec")))
803 friendly_error = error_type_str.split(
"> ")[0].replace(
"<",
"")
808 msg.setWindowTitle(_(
"Export Error"))
809 msg.setText(_(
"Sorry, there was an error exporting your video: \n%s") % friendly_error)
813 get_app().window.ExportEnded.emit(export_file_path)
816 self.timeline.Close()
819 self.timeline.ClearAllCache()
822 super(Export, self).
accept()
827 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.