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 os.environ[
'OS2_OMP_THREADS'] =
"0"
109 width =
get_app().window.timeline_sync.timeline.info.width
110 height =
get_app().window.timeline_sync.timeline.info.height
111 fps =
get_app().window.timeline_sync.timeline.info.fps
112 sample_rate =
get_app().window.timeline_sync.timeline.info.sample_rate
113 channels =
get_app().window.timeline_sync.timeline.info.channels
114 channel_layout =
get_app().window.timeline_sync.timeline.info.channel_layout
117 self.
timeline = openshot.Timeline(width, height, openshot.Fraction(fps.num, fps.den),
118 sample_rate, channels, channel_layout)
120 self.timeline.info.channel_layout =
get_app().window.timeline_sync.timeline.info.channel_layout
121 self.timeline.info.has_audio =
get_app().window.timeline_sync.timeline.info.has_audio
122 self.timeline.info.has_video =
get_app().window.timeline_sync.timeline.info.has_video
123 self.timeline.info.video_length =
get_app().window.timeline_sync.timeline.info.video_length
124 self.timeline.info.duration =
get_app().window.timeline_sync.timeline.info.duration
125 self.timeline.info.sample_rate =
get_app().window.timeline_sync.timeline.info.sample_rate
126 self.timeline.info.channels =
get_app().window.timeline_sync.timeline.info.channels
129 json_timeline = json.dumps(
get_app().project._data)
130 self.timeline.SetJson(json_timeline)
136 recommended_path = recommended_path = os.path.join(info.HOME_PATH)
137 if app.project.current_filepath:
138 recommended_path = os.path.dirname(app.project.current_filepath)
140 export_path =
get_app().project.get([
"export_path"])
141 if os.path.exists(export_path):
143 self.txtExportFolder.setText(export_path)
146 self.txtExportFolder.setText(recommended_path)
149 if not get_app().project.current_filepath:
151 self.txtFileName.setText(_(
"Untitled Project"))
155 parent_path, filename = os.path.split(
get_app().project.current_filepath)
156 filename, ext = os.path.splitext(filename)
157 self.txtFileName.setText(filename.replace(
"_",
" ").replace(
"-",
" ").capitalize())
160 self.txtImageFormat.setText(
"-%05d.png")
163 export_options = [_(
"Video & Audio"), _(
"Video Only"), _(
"Audio Only"), _(
"Image Sequence")]
164 for option
in export_options:
166 self.cboExportTo.addItem(option)
170 for layout
in [(openshot.LAYOUT_MONO, _(
"Mono (1 Channel)")),
171 (openshot.LAYOUT_STEREO, _(
"Stereo (2 Channel)")),
172 (openshot.LAYOUT_SURROUND, _(
"Surround (3 Channel)")),
173 (openshot.LAYOUT_5POINT1, _(
"Surround (5.1 Channel)")),
174 (openshot.LAYOUT_7POINT1, _(
"Surround (7.1 Channel)"))]:
176 self.channel_layout_choices.append(layout[0])
177 self.cboChannelLayout.addItem(layout[1], layout[0])
181 self.cboSimpleProjectType.currentIndexChanged.connect(
184 self.cboSimpleTarget.currentIndexChanged.connect(
186 self.cboSimpleVideoProfile.currentIndexChanged.connect(
188 self.cboSimpleQuality.currentIndexChanged.connect(
190 self.cboChannelLayout.currentIndexChanged.connect(self.
updateChannels)
197 for profile_folder
in [info.USER_PROFILES_PATH, info.PROFILES_PATH]:
198 for file
in os.listdir(profile_folder):
200 profile_path = os.path.join(profile_folder, file)
201 profile = openshot.Profile(profile_path)
204 profile_name =
"%s (%sx%s)" % (profile.info.description, profile.info.width, profile.info.height)
205 self.profile_names.append(profile_name)
209 self.profile_names.sort()
220 if app.project.get([
'profile'])
in profile_name:
230 for file
in os.listdir(info.EXPORT_PRESETS_DIR):
231 xmldoc = xml.parse(os.path.join(info.EXPORT_PRESETS_DIR, file))
232 type = xmldoc.getElementsByTagName(
"type")
233 presets.append(_(type[0].childNodes[0].data))
238 presets = list(set(presets))
239 for item
in sorted(presets):
240 self.cboSimpleProjectType.addItem(item, item)
241 if item == _(
"All Formats"):
242 selected_type = type_index
246 self.cboSimpleProjectType.setCurrentIndex(selected_type)
259 self.cboChannelLayout.currentIndexChanged.connect(self.
updateFrameRate)
268 self.delayed_fps_timer.stop()
271 fps_double = self.timeline.info.fps.ToDouble()
274 if self.
timeline and fps_double <= 300.0:
275 log.info(
"Valid framerate detected, sending to libopenshot: %s" % fps_double)
276 self.timeline.ApplyMapperToClips()
278 log.warning(
"Invalid framerate detected, not sending it to libopenshot: %s" % fps_double)
283 for profile, path
in self.profile_paths.items():
284 if profile_name
in profile:
290 for profile, path
in self.profile_paths.items():
291 if profile_path == path:
297 percentage_string =
"%4.1f%% " % (( current_frame - start_frame ) / ( end_frame - start_frame ) * 100)
298 self.progressExportVideo.setValue(current_frame)
299 self.progressExportVideo.setFormat(percentage_string)
300 self.setWindowTitle(
"%s %s" % (percentage_string, path))
305 log.info(
"updateChannels")
306 channels = self.txtChannels.value()
307 channel_layout = self.cboChannelLayout.currentData()
309 if channel_layout == openshot.LAYOUT_MONO:
311 elif channel_layout == openshot.LAYOUT_STEREO:
313 elif channel_layout == openshot.LAYOUT_SURROUND:
315 elif channel_layout == openshot.LAYOUT_5POINT1:
317 elif channel_layout == openshot.LAYOUT_7POINT1:
321 self.txtChannels.setValue(channels)
327 self.timeline.info.width = self.txtWidth.value()
328 self.timeline.info.height = self.txtHeight.value()
329 self.timeline.info.fps.num = self.txtFrameRateNum.value()
330 self.timeline.info.fps.den = self.txtFrameRateDen.value()
331 self.timeline.info.sample_rate = self.txtSampleRate.value()
332 self.timeline.info.channels = self.txtChannels.value()
333 self.timeline.info.channel_layout = self.cboChannelLayout.currentData()
336 self.delayed_fps_timer.start()
339 timeline_length = 0.0
340 fps = self.timeline.info.fps.ToFloat()
341 clips = self.timeline.Clips()
343 clip_last_frame = clip.Position() + clip.Duration()
344 if clip_last_frame > timeline_length:
346 timeline_length = clip_last_frame
352 self.txtStartFrame.setValue(1)
356 self.progressExportVideo.setMinimum(self.txtStartFrame.value())
357 self.progressExportVideo.setMaximum(self.txtEndFrame.value())
358 self.progressExportVideo.setValue(self.txtStartFrame.value())
361 selected_project = widget.itemData(index)
365 self.cboSimpleTarget.clear()
373 for file
in os.listdir(info.EXPORT_PRESETS_DIR):
374 xmldoc = xml.parse(os.path.join(info.EXPORT_PRESETS_DIR, file))
375 type = xmldoc.getElementsByTagName(
"type")
377 if _(type[0].childNodes[0].data) == selected_project:
378 titles = xmldoc.getElementsByTagName(
"title")
380 project_types.append(_(title.childNodes[0].data))
385 for item
in sorted(project_types):
386 self.cboSimpleTarget.addItem(item, item)
389 if item == _(
"MP4 (h.264)"):
390 selected_preset = preset_index
395 self.cboSimpleTarget.setCurrentIndex(selected_preset)
398 selected_profile_path = widget.itemData(index)
399 log.info(selected_profile_path)
406 profile = openshot.Profile(selected_profile_path)
409 self.txtWidth.setValue(profile.info.width)
410 self.txtHeight.setValue(profile.info.height)
411 self.txtFrameRateDen.setValue(profile.info.fps.den)
412 self.txtFrameRateNum.setValue(profile.info.fps.num)
413 self.txtAspectRatioNum.setValue(profile.info.display_ratio.num)
414 self.txtAspectRatioDen.setValue(profile.info.display_ratio.den)
415 self.txtPixelRatioNum.setValue(profile.info.pixel_ratio.num)
416 self.txtPixelRatioDen.setValue(profile.info.pixel_ratio.den)
419 self.cboInterlaced.clear()
420 self.cboInterlaced.addItem(_(
"Yes"),
"Yes")
421 self.cboInterlaced.addItem(_(
"No"),
"No")
422 if profile.info.interlaced_frame:
423 self.cboInterlaced.setCurrentIndex(0)
425 self.cboInterlaced.setCurrentIndex(1)
428 selected_target = widget.itemData(index)
429 log.info(selected_target)
440 previous_quality = self.cboSimpleQuality.currentIndex()
441 if previous_quality < 0:
442 previous_quality = self.cboSimpleQuality.count() - 1
443 previous_profile = self.cboSimpleVideoProfile.currentIndex()
444 if previous_profile < 0:
446 self.cboSimpleVideoProfile.clear()
447 self.cboSimpleQuality.clear()
452 for file
in os.listdir(info.EXPORT_PRESETS_DIR):
453 xmldoc = xml.parse(os.path.join(info.EXPORT_PRESETS_DIR, file))
454 title = xmldoc.getElementsByTagName(
"title")
455 if _(title[0].childNodes[0].data) == selected_target:
456 profiles = xmldoc.getElementsByTagName(
"projectprofile")
462 for profile
in profiles:
463 profiles_list.append(_(profile.childNodes[0].data))
468 profiles_list.append(profile_name)
471 videobitrate = xmldoc.getElementsByTagName(
"videobitrate")
472 for rate
in videobitrate:
473 v_l = rate.attributes[
"low"].value
474 v_m = rate.attributes[
"med"].value
475 v_h = rate.attributes[
"high"].value
476 self.
vbr = {_(
"Low"): v_l, _(
"Med"): v_m, _(
"High"): v_h}
479 audiobitrate = xmldoc.getElementsByTagName(
"audiobitrate")
480 for audiorate
in audiobitrate:
481 a_l = audiorate.attributes[
"low"].value
482 a_m = audiorate.attributes[
"med"].value
483 a_h = audiorate.attributes[
"high"].value
484 self.
abr = {_(
"Low"): a_l, _(
"Med"): a_m, _(
"High"): a_h}
487 vf = xmldoc.getElementsByTagName(
"videoformat")
488 self.txtVideoFormat.setText(vf[0].childNodes[0].data)
489 vc = xmldoc.getElementsByTagName(
"videocodec")
490 self.txtVideoCodec.setText(vc[0].childNodes[0].data)
491 sr = xmldoc.getElementsByTagName(
"samplerate")
492 self.txtSampleRate.setValue(int(sr[0].childNodes[0].data))
493 c = xmldoc.getElementsByTagName(
"audiochannels")
494 self.txtChannels.setValue(int(c[0].childNodes[0].data))
495 c = xmldoc.getElementsByTagName(
"audiochannellayout")
498 ac = xmldoc.getElementsByTagName(
"audiocodec")
499 audio_codec_name = ac[0].childNodes[0].data
500 if audio_codec_name ==
"aac":
502 if openshot.FFmpegWriter.IsValidCodec(
"libfaac"):
503 self.txtAudioCodec.setText(
"libfaac")
504 elif openshot.FFmpegWriter.IsValidCodec(
"libvo_aacenc"):
505 self.txtAudioCodec.setText(
"libvo_aacenc")
506 elif openshot.FFmpegWriter.IsValidCodec(
"aac"):
507 self.txtAudioCodec.setText(
"aac")
510 self.txtAudioCodec.setText(
"ac3")
513 self.txtAudioCodec.setText(audio_codec_name)
517 if layout == int(c[0].childNodes[0].data):
518 self.cboChannelLayout.setCurrentIndex(layout_index)
523 for item
in sorted(profiles_list):
528 self.cboSimpleVideoProfile.setCurrentIndex(previous_profile)
533 self.cboSimpleQuality.addItem(_(
"Low"),
"Low")
535 self.cboSimpleQuality.addItem(_(
"Med"),
"Med")
537 self.cboSimpleQuality.addItem(_(
"High"),
"High")
540 self.cboSimpleQuality.setCurrentIndex(previous_quality)
543 selected_profile_path = widget.itemData(index)
544 log.info(selected_profile_path)
558 self.cboProfile.setCurrentIndex(profile_index)
565 selected_quality = widget.itemData(index)
566 log.info(selected_quality)
574 self.txtVideoBitRate.setText(_(self.
vbr[_(selected_quality)]))
575 self.txtAudioBitrate.setText(_(self.
abr[_(selected_quality)]))
578 log.info(
"btnBrowse_clicked")
585 file_path = QFileDialog.getExistingDirectory(self, _(
"Choose a Folder..."), self.txtExportFolder.text())
586 if os.path.exists(file_path):
587 self.txtExportFolder.setText(file_path)
590 get_app().updates.update([
"export_path"], file_path)
596 s = BitRateString.lower().split(
" ")
602 raw_number_string = s[0]
603 raw_measurement = s[1]
606 raw_number = locale.atof(raw_number_string)
608 if "kb" in raw_measurement:
610 bit_rate_bytes = raw_number * 1000.0
612 elif "mb" in raw_measurement:
614 bit_rate_bytes = raw_number * 1000.0 * 1000.0
620 return str(int(bit_rate_bytes))
631 self.txtFileName.setEnabled(
False)
632 self.txtExportFolder.setEnabled(
False)
633 self.tabWidget.setEnabled(
False)
634 self.export_button.setEnabled(
False)
639 export_type = self.cboExportTo.currentText()
642 if export_type != _(
"Image Sequence"):
643 file_name_with_ext =
"%s.%s" % (self.txtFileName.text().strip(), self.txtVideoFormat.text().strip())
645 file_name_with_ext =
"%s%s" % (self.txtFileName.text().strip(), self.txtImageFormat.text().strip())
646 export_file_path = os.path.join(self.txtExportFolder.text().strip(), file_name_with_ext)
647 log.info(export_file_path)
652 file = File.get(path=export_file_path)
654 ret = QMessageBox.question(self, _(
"Export Video"), _(
"%s is an input file.\nPlease choose a different name.") % file_name_with_ext,
656 self.txtFileName.setEnabled(
True)
657 self.txtExportFolder.setEnabled(
True)
658 self.tabWidget.setEnabled(
True)
659 self.export_button.setEnabled(
True)
664 if os.path.exists(export_file_path)
and export_type
in [_(
"Video & Audio"), _(
"Video Only"), _(
"Audio Only")]:
666 ret = QMessageBox.question(self, _(
"Export Video"), _(
"%s already exists.\nDo you want to replace it?") % file_name_with_ext,
667 QMessageBox.No | QMessageBox.Yes)
668 if ret == QMessageBox.No:
671 self.txtFileName.setEnabled(
True)
672 self.txtExportFolder.setEnabled(
True)
673 self.tabWidget.setEnabled(
True)
674 self.export_button.setEnabled(
True)
679 video_settings = {
"vformat": self.txtVideoFormat.text(),
680 "vcodec": self.txtVideoCodec.text(),
681 "fps": {
"num" : self.txtFrameRateNum.value(),
"den": self.txtFrameRateDen.value()},
682 "width": self.txtWidth.value(),
683 "height": self.txtHeight.value(),
684 "pixel_ratio": {
"num": self.txtPixelRatioNum.value(),
"den": self.txtPixelRatioDen.value()},
686 "start_frame": self.txtStartFrame.value(),
687 "end_frame": self.txtEndFrame.value() + 1
690 audio_settings = {
"acodec": self.txtAudioCodec.text(),
691 "sample_rate": self.txtSampleRate.value(),
692 "channels": self.txtChannels.value(),
693 "channel_layout": self.cboChannelLayout.currentData(),
698 if export_type == _(
"Image Sequence"):
699 image_ext = os.path.splitext(self.txtImageFormat.text().strip())[1].replace(
".",
"")
700 video_settings[
"vformat"] = image_ext
701 if image_ext
in [
"jpg",
"jpeg"]:
702 video_settings[
"vcodec"] =
"mjpeg"
704 video_settings[
"vcodec"] = image_ext
707 self.timeline.SetMaxSize(video_settings.get(
"width"), video_settings.get(
"height"))
710 export_cache_object = openshot.CacheMemory(250)
711 self.timeline.SetCache(export_cache_object)
715 w = openshot.FFmpegWriter(export_file_path)
718 if export_type
in [_(
"Video & Audio"), _(
"Video Only"), _(
"Image Sequence")]:
719 w.SetVideoOptions(
True,
720 video_settings.get(
"vcodec"),
721 openshot.Fraction(video_settings.get(
"fps").get(
"num"),
722 video_settings.get(
"fps").get(
"den")),
723 video_settings.get(
"width"),
724 video_settings.get(
"height"),
725 openshot.Fraction(video_settings.get(
"pixel_ratio").get(
"num"),
726 video_settings.get(
"pixel_ratio").get(
"den")),
729 video_settings.get(
"video_bitrate"))
732 if export_type
in [_(
"Video & Audio"), _(
"Audio Only")]:
733 w.SetAudioOptions(
True,
734 audio_settings.get(
"acodec"),
735 audio_settings.get(
"sample_rate"),
736 audio_settings.get(
"channels"),
737 audio_settings.get(
"channel_layout"),
738 audio_settings.get(
"audio_bitrate"))
744 export_file_path =
""
745 get_app().window.ExportStarted.emit(export_file_path, video_settings.get(
"start_frame"), video_settings.get(
"end_frame"))
747 progressstep = max(1 , round(( video_settings.get(
"end_frame") - video_settings.get(
"start_frame") ) / 1000))
748 start_time_export = time.time()
749 start_frame_export = video_settings.get(
"start_frame")
750 end_frame_export = video_settings.get(
"end_frame")
752 for frame
in range(video_settings.get(
"start_frame"), video_settings.get(
"end_frame")):
754 if (frame % progressstep) == 0:
755 end_time_export = time.time()
756 if ((( frame - start_frame_export ) != 0) & (( end_time_export - start_time_export ) != 0)):
757 seconds_left = round(( start_time_export - end_time_export )*( frame - end_frame_export )/( frame - start_frame_export ))
758 fps_encode = ((frame - start_frame_export)/(end_time_export-start_time_export))
759 export_file_path = _(
"%(hours)d:%(minutes)02d:%(seconds)02d Remaining (%(fps)5.2f FPS)") % {
'hours' : seconds_left / 3600,
760 'minutes': (seconds_left / 60) % 60,
761 'seconds': seconds_left % 60,
763 get_app().window.ExportFrame.emit(export_file_path, video_settings.get(
"start_frame"), video_settings.get(
"end_frame"), frame)
766 QCoreApplication.processEvents()
769 w.WriteFrame(self.timeline.GetFrame(frame))
779 except Exception
as e:
782 error_type_str = str(e)
783 log.info(
"Error type string: %s" % error_type_str)
785 if "InvalidChannels" in error_type_str:
786 log.info(
"Error setting invalid # of channels (%s)" % (audio_settings.get(
"channels")))
787 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")))
789 elif "InvalidSampleRate" in error_type_str:
790 log.info(
"Error setting invalid sample rate (%s)" % (audio_settings.get(
"sample_rate")))
791 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")))
793 elif "InvalidFormat" in error_type_str:
794 log.info(
"Error setting invalid format (%s)" % (video_settings.get(
"vformat")))
797 elif "InvalidCodec" in error_type_str:
798 log.info(
"Error setting invalid codec (%s/%s/%s)" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec")))
799 track_metric_error(
"invalid-codec-%s-%s-%s" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec")))
801 elif "ErrorEncodingVideo" in error_type_str:
802 log.info(
"Error encoding video frame (%s/%s/%s)" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec")))
803 track_metric_error(
"video-encode-%s-%s-%s" % (video_settings.get(
"vformat"), video_settings.get(
"vcodec"), audio_settings.get(
"acodec")))
806 friendly_error = error_type_str.split(
"> ")[0].replace(
"<",
"")
811 msg.setWindowTitle(_(
"Export Error"))
812 msg.setText(_(
"Sorry, there was an error exporting your video: \n%s") % friendly_error)
816 get_app().window.ExportEnded.emit(export_file_path)
819 self.timeline.Close()
822 self.timeline.ClearAllCache()
825 if self.s.get(
"omp_threads_enabled"):
826 os.environ[
'OS2_OMP_THREADS'] =
"1"
828 os.environ[
'OS2_OMP_THREADS'] =
"0"
831 super(Export, self).
accept()
835 if self.s.get(
"omp_threads_enabled"):
836 os.environ[
'OS2_OMP_THREADS'] =
"1"
838 os.environ[
'OS2_OMP_THREADS'] =
"0"
842 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.