From a8f7fb667b973ea43ffd392d55ec988178d4ae95 Mon Sep 17 00:00:00 2001 From: Rodrigo Braz Monteiro Date: Mon, 16 Jan 2006 21:02:54 +0000 Subject: [PATCH] Originally committed to SVN as r2. --- automation/automation-lua.txt | 548 ++++ automation/demos/1-minimal.lua | 16 + automation/demos/2-dump.lua | 43 + automation/demos/3-include.lua | 16 + automation/demos/4-text_extents.lua | 55 + automation/demos/5-configuration.lua | 72 + automation/demos/6-simple-effect.lua | 87 + automation/demos/7-advanced-effect.lua | 189 ++ automation/demos/8-skeleton.lua | 18 + automation/demos/9-skeleton-advanced.lua | 49 + automation/demos/readme.txt | 18 + automation/factorybrew/line-per-syllable.lua | 158 + automation/factorybrew/readme.txt | 12 + automation/factorybrew/simple-k-replacer.lua | 143 + automation/include/karaskel-adv.lua | 64 + automation/include/karaskel.lua | 183 ++ automation/include/readme.txt | 10 + automation/include/utils.lua | 167 + code-docs/export-system-new-idea.vsd | Bin 0 -> 50176 bytes code-docs/export-system-new.vsd | Bin 0 -> 89088 bytes code-docs/export-system-original.vsd | Bin 0 -> 61952 bytes core/about.cpp | 92 + core/about.h | 52 + core/aegisublocale.cpp | 128 + core/aegisublocale.h | 61 + core/aspell_wrap.cpp | 94 + core/aspell_wrap.h | 68 + core/ass_dialogue.cpp | 819 +++++ core/ass_dialogue.h | 186 ++ core/ass_entry.cpp | 76 + core/ass_entry.h | 80 + core/ass_export_filter.cpp | 177 ++ core/ass_export_filter.h | 105 + core/ass_exporter.cpp | 180 ++ core/ass_exporter.h | 80 + core/ass_file.cpp | 1021 ++++++ core/ass_file.h | 136 + core/ass_override.cpp | 691 +++++ core/ass_override.h | 142 + core/ass_style.cpp | 528 ++++ core/ass_style.h | 106 + core/ass_style_storage.cpp | 111 + core/ass_style_storage.h | 64 + core/ass_time.cpp | 261 ++ core/ass_time.h | 74 + core/audio_box.cpp | 527 ++++ core/audio_box.h | 159 + core/audio_display.cpp | 1762 +++++++++++ core/audio_display.h | 165 + core/audio_karaoke.cpp | 548 ++++ core/audio_karaoke.h | 118 + core/audio_provider.cpp | 605 ++++ core/audio_provider.h | 143 + core/automation.cpp | 1595 ++++++++++ core/automation.h | 155 + core/automation_filter.cpp | 293 ++ core/automation_filter.h | 85 + core/automation_gui.cpp | 376 +++ core/automation_gui.h | 87 + core/avisynth.h | 749 +++++ core/avisynth_wrap.cpp | 87 + core/avisynth_wrap.h | 67 + core/bitmaps/automation.bmp | Bin 0 -> 238 bytes core/bitmaps/button_audio_commit.bmp | Bin 0 -> 238 bytes core/bitmaps/button_audio_go.bmp | Bin 0 -> 238 bytes core/bitmaps/button_bold.bmp | Bin 0 -> 214 bytes core/bitmaps/button_color1.bmp | Bin 0 -> 262 bytes core/bitmaps/button_color2.bmp | Bin 0 -> 262 bytes core/bitmaps/button_color3.bmp | Bin 0 -> 262 bytes core/bitmaps/button_color4.bmp | Bin 0 -> 262 bytes core/bitmaps/button_fontname.bmp | Bin 0 -> 262 bytes core/bitmaps/button_italics.bmp | Bin 0 -> 214 bytes core/bitmaps/button_leadin.bmp | Bin 0 -> 238 bytes core/bitmaps/button_leadout.bmp | Bin 0 -> 238 bytes core/bitmaps/button_next.bmp | Bin 0 -> 238 bytes core/bitmaps/button_play.bmp | Bin 0 -> 238 bytes core/bitmaps/button_play500after.bmp | Bin 0 -> 238 bytes core/bitmaps/button_play500before.bmp | Bin 0 -> 238 bytes core/bitmaps/button_playfirst500.bmp | Bin 0 -> 238 bytes core/bitmaps/button_playlast500.bmp | Bin 0 -> 238 bytes core/bitmaps/button_playline.bmp | Bin 0 -> 238 bytes core/bitmaps/button_playsel.bmp | Bin 0 -> 238 bytes core/bitmaps/button_playtoend.bmp | Bin 0 -> 238 bytes core/bitmaps/button_prev.bmp | Bin 0 -> 238 bytes core/bitmaps/button_stop.bmp | Bin 0 -> 238 bytes core/bitmaps/button_strikeout.bmp | Bin 0 -> 214 bytes core/bitmaps/button_underline.bmp | Bin 0 -> 214 bytes core/bitmaps/contents.bmp | Bin 0 -> 1398 bytes core/bitmaps/copy.bmp | Bin 0 -> 1318 bytes core/bitmaps/copy_disable.bmp | Bin 0 -> 1318 bytes core/bitmaps/cut.bmp | Bin 0 -> 1318 bytes core/bitmaps/cut_disable.bmp | Bin 0 -> 1318 bytes core/bitmaps/find.bmp | Bin 0 -> 1318 bytes core/bitmaps/fontcollect.bmp | Bin 0 -> 1318 bytes core/bitmaps/hotkeys.bmp | Bin 0 -> 1318 bytes core/bitmaps/icon.ico | Bin 0 -> 25214 bytes core/bitmaps/irc.bmp | Bin 0 -> 1318 bytes core/bitmaps/jumpto.bmp | Bin 0 -> 1318 bytes core/bitmaps/jumpto_disable.bmp | Bin 0 -> 1318 bytes core/bitmaps/new.bmp | Bin 0 -> 238 bytes core/bitmaps/null_button.bmp | Bin 0 -> 1318 bytes core/bitmaps/open.bmp | Bin 0 -> 238 bytes core/bitmaps/paste.bmp | Bin 0 -> 1318 bytes core/bitmaps/paste_disable.bmp | Bin 0 -> 1318 bytes core/bitmaps/properties.bmp | Bin 0 -> 1318 bytes core/bitmaps/resample.bmp | Bin 0 -> 1318 bytes core/bitmaps/save.bmp | Bin 0 -> 238 bytes core/bitmaps/select_visible.bmp | Bin 0 -> 1318 bytes core/bitmaps/shift_to_frame.bmp | Bin 0 -> 1318 bytes core/bitmaps/shift_to_frame_disable.bmp | Bin 0 -> 1318 bytes core/bitmaps/snap_to_scene.bmp | Bin 0 -> 1318 bytes core/bitmaps/snap_to_scene_disable.bmp | Bin 0 -> 1318 bytes core/bitmaps/spellcheck.bmp | Bin 0 -> 1318 bytes core/bitmaps/splash_01.bmp | Bin 0 -> 97080 bytes core/bitmaps/splash_02.bmp | Bin 0 -> 97078 bytes core/bitmaps/splash_03.bmp | Bin 0 -> 97078 bytes core/bitmaps/splash_04.bmp | Bin 0 -> 97080 bytes core/bitmaps/splash_05.bmp | Bin 0 -> 97080 bytes core/bitmaps/style_manager.bmp | Bin 0 -> 1318 bytes core/bitmaps/styling_assistant.bmp | Bin 0 -> 1318 bytes core/bitmaps/subend_to_video.bmp | Bin 0 -> 1318 bytes core/bitmaps/subend_to_video_disable.bmp | Bin 0 -> 1318 bytes core/bitmaps/substart_to_video.bmp | Bin 0 -> 1318 bytes core/bitmaps/substart_to_video_disable.bmp | Bin 0 -> 1318 bytes core/bitmaps/timing_processor.bmp | Bin 0 -> 1318 bytes core/bitmaps/toggle_audio_autocommit.bmp | Bin 0 -> 298 bytes core/bitmaps/toggle_audio_autoscroll.bmp | Bin 0 -> 1378 bytes core/bitmaps/toggle_audio_spectrum.bmp | Bin 0 -> 298 bytes core/bitmaps/toggle_audio_ssa.bmp | Bin 0 -> 1378 bytes core/bitmaps/toggle_tag_hiding.bmp | Bin 0 -> 1318 bytes core/bitmaps/toggle_video_autoscroll.bmp | Bin 0 -> 1378 bytes core/bitmaps/translation.bmp | Bin 0 -> 1318 bytes core/bitmaps/undo.bmp | Bin 0 -> 1318 bytes core/bitmaps/undo_disable.bmp | Bin 0 -> 1318 bytes core/bitmaps/video_to_subend.bmp | Bin 0 -> 1318 bytes core/bitmaps/video_to_subend_disable.bmp | Bin 0 -> 1318 bytes core/bitmaps/video_to_substart.bmp | Bin 0 -> 1318 bytes core/bitmaps/video_to_substart_disable.bmp | Bin 0 -> 1318 bytes core/bitmaps/zoom_in.bmp | Bin 0 -> 1318 bytes core/bitmaps/zoom_out.bmp | Bin 0 -> 1318 bytes core/changelog.txt | 317 ++ core/colorspace.cpp | 364 +++ core/colorspace.h | 78 + core/dialog_associations.cpp | 129 + core/dialog_associations.h | 60 + core/dialog_colorpicker.cpp | 1267 ++++++++ core/dialog_colorpicker.h | 228 ++ core/dialog_export.cpp | 301 ++ core/dialog_export.h | 96 + core/dialog_hotkeys.cpp | 213 ++ core/dialog_hotkeys.h | 111 + core/dialog_jumpto.cpp | 181 ++ core/dialog_jumpto.h | 101 + core/dialog_progress.cpp | 150 + core/dialog_progress.h | 83 + core/dialog_properties.cpp | 197 ++ core/dialog_properties.h | 87 + core/dialog_resample.cpp | 245 ++ core/dialog_resample.h | 86 + core/dialog_search_replace.cpp | 526 ++++ core/dialog_search_replace.h | 125 + core/dialog_selection.cpp | 290 ++ core/dialog_selection.h | 81 + core/dialog_shift_times.cpp | 335 ++ core/dialog_shift_times.h | 101 + core/dialog_spellcheck.cpp | 577 ++++ core/dialog_spellcheck.h | 175 ++ core/dialog_style_editor.cpp | 462 +++ core/dialog_style_editor.h | 146 + core/dialog_style_manager.cpp | 635 ++++ core/dialog_style_manager.h | 131 + core/dialog_styling_assistant.cpp | 409 +++ core/dialog_styling_assistant.h | 119 + core/dialog_timing_processor.cpp | 515 +++ core/dialog_timing_processor.h | 111 + core/dialog_translation.cpp | 381 +++ core/dialog_translation.h | 116 + core/drop.cpp | 56 + core/drop.h | 65 + core/export_clean_info.cpp | 114 + core/export_clean_info.h | 65 + core/export_fixstyle.cpp | 95 + core/export_fixstyle.h | 55 + core/export_framerate.cpp | 272 ++ core/export_framerate.h | 92 + core/fft.cpp | 190 ++ core/fft.h | 59 + core/fonts_collector.cpp | 379 +++ core/fonts_collector.h | 114 + core/frame_main.cpp | 1108 +++++++ core/frame_main.h | 331 ++ core/frame_main_events.cpp | 1185 +++++++ core/hilimod_textctrl.cpp | 93 + core/hilimod_textctrl.h | 65 + core/hotkeys.cpp | 458 +++ core/hotkeys.h | 108 + core/main.cpp | 302 ++ core/main.h | 84 + core/options.cpp | 430 +++ core/options.h | 83 + core/readme.txt | 90 + core/res.rc | 120 + core/splash.cpp | 117 + core/splash.h | 66 + core/static_bmp.cpp | 66 + core/static_bmp.h | 56 + core/string_codec.cpp | 79 + core/string_codec.h | 64 + core/subs_edit_box.cpp | 1323 ++++++++ core/subs_edit_box.h | 213 ++ core/subs_grid.cpp | 1800 +++++++++++ core/subs_grid.h | 178 ++ core/text_file_reader.cpp | 238 ++ core/text_file_reader.h | 76 + core/text_file_writer.cpp | 145 + core/text_file_writer.h | 72 + core/timeedit_ctrl.cpp | 170 + core/timeedit_ctrl.h | 70 + core/tip.cpp | 104 + core/tip.h | 60 + core/todo.txt | 41 + core/toggle_bitmap.cpp | 147 + core/toggle_bitmap.h | 66 + core/utils.cpp | 125 + core/utils.h | 60 + core/validators.cpp | 59 + core/validators.h | 54 + core/variable_data.cpp | 256 ++ core/variable_data.h | 103 + core/version.h | 41 + core/vfr.cpp | 507 +++ core/vfr.h | 135 + core/vfw_wrap.cpp | 90 + core/vfw_wrap.h | 50 + core/video_box.cpp | 100 + core/video_box.h | 72 + core/video_display.cpp | 1186 +++++++ core/video_display.h | 168 + core/video_slider.cpp | 473 +++ core/video_slider.h | 93 + core/video_zoom.cpp | 68 + core/video_zoom.h | 64 + core/yatta_wrap.cpp | 81 + core/yatta_wrap.h | 54 + docs/Bezier.png | Bin 0 -> 4634 bytes docs/aegisub_styling_assistant.png | Bin 0 -> 245698 bytes docs/aegisub_subtitle_field_colors.png | Bin 0 -> 4061 bytes docs/aegisub_subtitle_hidden_tags.png | Bin 0 -> 3223 bytes docs/aegisub_subtitle_not_hidden_tags.png | Bin 0 -> 3519 bytes docs/anamorphic.png | Bin 0 -> 1795 bytes docs/audiohigh.png | Bin 0 -> 23856 bytes docs/audiomode.png | Bin 0 -> 23575 bytes docs/audiomode_det.png | Bin 0 -> 18382 bytes docs/audioview-basic.png | Bin 0 -> 29237 bytes docs/audioview-karaoke-1.png | Bin 0 -> 29593 bytes docs/audioview-karaoke-2.png | Bin 0 -> 31064 bytes docs/audioview-split-dialogue.png | Bin 0 -> 2195 bytes docs/audioview-timing.png | Bin 0 -> 28243 bytes docs/automation-demo-6.png | Bin 0 -> 48097 bytes docs/automation-demo-7.jpg | Bin 0 -> 36422 bytes docs/automation-export-config.png | Bin 0 -> 24985 bytes docs/automation-manager.png | Bin 0 -> 14237 bytes docs/automation-script-lps.png | Bin 0 -> 20065 bytes docs/button_bold.png | Bin 0 -> 2883 bytes docs/button_closedbook.gif | Bin 0 -> 996 bytes docs/button_fonts.png | Bin 0 -> 2915 bytes docs/button_italics.png | Bin 0 -> 2876 bytes docs/button_main.gif | Bin 0 -> 1061 bytes docs/button_main_d.gif | Bin 0 -> 1011 bytes docs/button_main_h.gif | Bin 0 -> 993 bytes docs/button_next.gif | Bin 0 -> 1061 bytes docs/button_next_d.gif | Bin 0 -> 1015 bytes docs/button_next_h.gif | Bin 0 -> 993 bytes docs/button_openbook.gif | Bin 0 -> 996 bytes docs/button_outline_color.png | Bin 0 -> 2947 bytes docs/button_prev.gif | Bin 0 -> 1058 bytes docs/button_prev_d.gif | Bin 0 -> 1012 bytes docs/button_prev_h.gif | Bin 0 -> 993 bytes docs/button_primary_color.png | Bin 0 -> 2928 bytes docs/button_secondary_color.png | Bin 0 -> 2929 bytes docs/button_shadow_color.png | Bin 0 -> 2943 bytes docs/button_strikeout.png | Bin 0 -> 2889 bytes docs/button_topic.gif | Bin 0 -> 924 bytes docs/button_underline.png | Bin 0 -> 2874 bytes docs/clip_sample01.jpg | Bin 0 -> 23215 bytes docs/color-picker.png | Bin 0 -> 31889 bytes docs/example_button_v1.png | Bin 0 -> 3658 bytes docs/example_button_v2.png | Bin 0 -> 3561 bytes docs/example_button_v3.png | Bin 0 -> 3549 bytes docs/feature comparison.xls | Bin 0 -> 29696 bytes docs/fonts_collector_01.png | Bin 0 -> 9089 bytes docs/fonts_collector_02.png | Bin 0 -> 14158 bytes docs/fr_sample01.jpg | Bin 0 -> 6331 bytes docs/fr_sample02.jpg | Bin 0 -> 5794 bytes docs/fr_sample03.jpg | Bin 0 -> 5794 bytes docs/help.hm3 | Bin 0 -> 375296 bytes docs/jump.png | Bin 0 -> 876 bytes docs/karmode.png | Bin 0 -> 26301 bytes docs/main_window_subtitles.png | Bin 0 -> 14127 bytes docs/nonscroll.js | 46 + docs/pos_sample01.jpg | Bin 0 -> 28621 bytes docs/pos_sample02.jpg | Bin 0 -> 28548 bytes docs/pos_sample03.jpg | Bin 0 -> 28569 bytes docs/right_click_menu_subtitle_field.png | Bin 0 -> 13859 bytes docs/seek.png | Bin 0 -> 816 bytes docs/splash.PNG | Bin 0 -> 68084 bytes docs/style_manager_1.png | Bin 0 -> 9756 bytes docs/style_manager_2.png | Bin 0 -> 9360 bytes docs/styles_multicolor_1.jpg | Bin 0 -> 77695 bytes docs/styles_multicolor_2.jpg | Bin 0 -> 59938 bytes docs/styles_multicolor_aegisub_1.png | Bin 0 -> 4566 bytes docs/styles_multicolor_aegisub_2.png | Bin 0 -> 3480 bytes docs/styles_standard.jpg | Bin 0 -> 77861 bytes docs/styles_standard_aegisub.png | Bin 0 -> 4690 bytes docs/styling_assistant.png | Bin 0 -> 9985 bytes docs/styling_assistant_autocomplete_v1.png | Bin 0 -> 3398 bytes docs/styling_assistant_autocomplete_v2.png | Bin 0 -> 3207 bytes docs/subtitle_basics_Make_times_continous.png | Bin 0 -> 6078 bytes docs/subtitle_basics_Recombine_v1.png | Bin 0 -> 5756 bytes docs/subtitle_basics_Recombine_v2.png | Bin 0 -> 5774 bytes docs/subtitle_basics_Recombine_v3.png | Bin 0 -> 6101 bytes docs/subtitle_basics_join_as_karaoke.png | Bin 0 -> 5858 bytes docs/subtitle_basics_join_concatenate.png | Bin 0 -> 5498 bytes docs/subtitle_basics_join_keep_first.png | Bin 0 -> 5372 bytes docs/subtitle_basics_swap.png | Bin 0 -> 5270 bytes docs/subtitle_display_window.png | Bin 0 -> 3903 bytes docs/subtitle_editing_window.png | Bin 0 -> 4935 bytes docs/subtitle_options.png | Bin 0 -> 4107 bytes docs/translation.png | Bin 0 -> 11838 bytes docs/typeset_01.jpg | Bin 0 -> 11537 bytes docs/typeset_02.jpg | Bin 0 -> 13561 bytes docs/typeset_03.jpg | Bin 0 -> 12987 bytes docs/typeset_04.jpg | Bin 0 -> 13124 bytes docs/typeset_05.jpg | Bin 0 -> 14735 bytes docs/typeset_06.jpg | Bin 0 -> 13355 bytes docs/typeset_07.jpg | Bin 0 -> 13894 bytes docs/typeset_example01.jpg | Bin 0 -> 57780 bytes docs/typeset_example02.jpg | Bin 0 -> 51629 bytes docs/typeset_example03.jpg | Bin 0 -> 36509 bytes docs/typeset_example04.jpg | Bin 0 -> 34713 bytes docs/vfr.png | Bin 0 -> 1185 bytes docs/videomode.png | Bin 0 -> 183566 bytes docs/zoom.png | Bin 0 -> 814 bytes installer/aegisub.nsi | 528 ++++ locale/de/aegisub.mo | Bin 0 -> 46799 bytes locale/de/aegisub.po | 2747 +++++++++++++++++ locale/de/wxstd.mo | Bin 0 -> 78884 bytes locale/fr/aegisub.mo | Bin 0 -> 49005 bytes locale/fr/aegisub.po | 2660 ++++++++++++++++ locale/fr/wxstd.mo | Bin 0 -> 91269 bytes locale/pt_BR/aegisub.mo | Bin 0 -> 47522 bytes locale/pt_BR/aegisub.po | 2654 ++++++++++++++++ locale/pt_BR/wxstd.mo | Bin 0 -> 83237 bytes locale/ru/aegisub.mo | Bin 0 -> 58559 bytes locale/ru/aegisub.po | 2658 ++++++++++++++++ locale/ru/wxstd.mo | Bin 0 -> 87113 bytes 356 files changed, 50526 insertions(+) create mode 100644 automation/automation-lua.txt create mode 100644 automation/demos/1-minimal.lua create mode 100644 automation/demos/2-dump.lua create mode 100644 automation/demos/3-include.lua create mode 100644 automation/demos/4-text_extents.lua create mode 100644 automation/demos/5-configuration.lua create mode 100644 automation/demos/6-simple-effect.lua create mode 100644 automation/demos/7-advanced-effect.lua create mode 100644 automation/demos/8-skeleton.lua create mode 100644 automation/demos/9-skeleton-advanced.lua create mode 100644 automation/demos/readme.txt create mode 100644 automation/factorybrew/line-per-syllable.lua create mode 100644 automation/factorybrew/readme.txt create mode 100644 automation/factorybrew/simple-k-replacer.lua create mode 100644 automation/include/karaskel-adv.lua create mode 100644 automation/include/karaskel.lua create mode 100644 automation/include/readme.txt create mode 100644 automation/include/utils.lua create mode 100644 code-docs/export-system-new-idea.vsd create mode 100644 code-docs/export-system-new.vsd create mode 100644 code-docs/export-system-original.vsd create mode 100644 core/about.cpp create mode 100644 core/about.h create mode 100644 core/aegisublocale.cpp create mode 100644 core/aegisublocale.h create mode 100644 core/aspell_wrap.cpp create mode 100644 core/aspell_wrap.h create mode 100644 core/ass_dialogue.cpp create mode 100644 core/ass_dialogue.h create mode 100644 core/ass_entry.cpp create mode 100644 core/ass_entry.h create mode 100644 core/ass_export_filter.cpp create mode 100644 core/ass_export_filter.h create mode 100644 core/ass_exporter.cpp create mode 100644 core/ass_exporter.h create mode 100644 core/ass_file.cpp create mode 100644 core/ass_file.h create mode 100644 core/ass_override.cpp create mode 100644 core/ass_override.h create mode 100644 core/ass_style.cpp create mode 100644 core/ass_style.h create mode 100644 core/ass_style_storage.cpp create mode 100644 core/ass_style_storage.h create mode 100644 core/ass_time.cpp create mode 100644 core/ass_time.h create mode 100644 core/audio_box.cpp create mode 100644 core/audio_box.h create mode 100644 core/audio_display.cpp create mode 100644 core/audio_display.h create mode 100644 core/audio_karaoke.cpp create mode 100644 core/audio_karaoke.h create mode 100644 core/audio_provider.cpp create mode 100644 core/audio_provider.h create mode 100644 core/automation.cpp create mode 100644 core/automation.h create mode 100644 core/automation_filter.cpp create mode 100644 core/automation_filter.h create mode 100644 core/automation_gui.cpp create mode 100644 core/automation_gui.h create mode 100644 core/avisynth.h create mode 100644 core/avisynth_wrap.cpp create mode 100644 core/avisynth_wrap.h create mode 100644 core/bitmaps/automation.bmp create mode 100644 core/bitmaps/button_audio_commit.bmp create mode 100644 core/bitmaps/button_audio_go.bmp create mode 100644 core/bitmaps/button_bold.bmp create mode 100644 core/bitmaps/button_color1.bmp create mode 100644 core/bitmaps/button_color2.bmp create mode 100644 core/bitmaps/button_color3.bmp create mode 100644 core/bitmaps/button_color4.bmp create mode 100644 core/bitmaps/button_fontname.bmp create mode 100644 core/bitmaps/button_italics.bmp create mode 100644 core/bitmaps/button_leadin.bmp create mode 100644 core/bitmaps/button_leadout.bmp create mode 100644 core/bitmaps/button_next.bmp create mode 100644 core/bitmaps/button_play.bmp create mode 100644 core/bitmaps/button_play500after.bmp create mode 100644 core/bitmaps/button_play500before.bmp create mode 100644 core/bitmaps/button_playfirst500.bmp create mode 100644 core/bitmaps/button_playlast500.bmp create mode 100644 core/bitmaps/button_playline.bmp create mode 100644 core/bitmaps/button_playsel.bmp create mode 100644 core/bitmaps/button_playtoend.bmp create mode 100644 core/bitmaps/button_prev.bmp create mode 100644 core/bitmaps/button_stop.bmp create mode 100644 core/bitmaps/button_strikeout.bmp create mode 100644 core/bitmaps/button_underline.bmp create mode 100644 core/bitmaps/contents.bmp create mode 100644 core/bitmaps/copy.bmp create mode 100644 core/bitmaps/copy_disable.bmp create mode 100644 core/bitmaps/cut.bmp create mode 100644 core/bitmaps/cut_disable.bmp create mode 100644 core/bitmaps/find.bmp create mode 100644 core/bitmaps/fontcollect.bmp create mode 100644 core/bitmaps/hotkeys.bmp create mode 100644 core/bitmaps/icon.ico create mode 100644 core/bitmaps/irc.bmp create mode 100644 core/bitmaps/jumpto.bmp create mode 100644 core/bitmaps/jumpto_disable.bmp create mode 100644 core/bitmaps/new.bmp create mode 100644 core/bitmaps/null_button.bmp create mode 100644 core/bitmaps/open.bmp create mode 100644 core/bitmaps/paste.bmp create mode 100644 core/bitmaps/paste_disable.bmp create mode 100644 core/bitmaps/properties.bmp create mode 100644 core/bitmaps/resample.bmp create mode 100644 core/bitmaps/save.bmp create mode 100644 core/bitmaps/select_visible.bmp create mode 100644 core/bitmaps/shift_to_frame.bmp create mode 100644 core/bitmaps/shift_to_frame_disable.bmp create mode 100644 core/bitmaps/snap_to_scene.bmp create mode 100644 core/bitmaps/snap_to_scene_disable.bmp create mode 100644 core/bitmaps/spellcheck.bmp create mode 100644 core/bitmaps/splash_01.bmp create mode 100644 core/bitmaps/splash_02.bmp create mode 100644 core/bitmaps/splash_03.bmp create mode 100644 core/bitmaps/splash_04.bmp create mode 100644 core/bitmaps/splash_05.bmp create mode 100644 core/bitmaps/style_manager.bmp create mode 100644 core/bitmaps/styling_assistant.bmp create mode 100644 core/bitmaps/subend_to_video.bmp create mode 100644 core/bitmaps/subend_to_video_disable.bmp create mode 100644 core/bitmaps/substart_to_video.bmp create mode 100644 core/bitmaps/substart_to_video_disable.bmp create mode 100644 core/bitmaps/timing_processor.bmp create mode 100644 core/bitmaps/toggle_audio_autocommit.bmp create mode 100644 core/bitmaps/toggle_audio_autoscroll.bmp create mode 100644 core/bitmaps/toggle_audio_spectrum.bmp create mode 100644 core/bitmaps/toggle_audio_ssa.bmp create mode 100644 core/bitmaps/toggle_tag_hiding.bmp create mode 100644 core/bitmaps/toggle_video_autoscroll.bmp create mode 100644 core/bitmaps/translation.bmp create mode 100644 core/bitmaps/undo.bmp create mode 100644 core/bitmaps/undo_disable.bmp create mode 100644 core/bitmaps/video_to_subend.bmp create mode 100644 core/bitmaps/video_to_subend_disable.bmp create mode 100644 core/bitmaps/video_to_substart.bmp create mode 100644 core/bitmaps/video_to_substart_disable.bmp create mode 100644 core/bitmaps/zoom_in.bmp create mode 100644 core/bitmaps/zoom_out.bmp create mode 100644 core/changelog.txt create mode 100644 core/colorspace.cpp create mode 100644 core/colorspace.h create mode 100644 core/dialog_associations.cpp create mode 100644 core/dialog_associations.h create mode 100644 core/dialog_colorpicker.cpp create mode 100644 core/dialog_colorpicker.h create mode 100644 core/dialog_export.cpp create mode 100644 core/dialog_export.h create mode 100644 core/dialog_hotkeys.cpp create mode 100644 core/dialog_hotkeys.h create mode 100644 core/dialog_jumpto.cpp create mode 100644 core/dialog_jumpto.h create mode 100644 core/dialog_progress.cpp create mode 100644 core/dialog_progress.h create mode 100644 core/dialog_properties.cpp create mode 100644 core/dialog_properties.h create mode 100644 core/dialog_resample.cpp create mode 100644 core/dialog_resample.h create mode 100644 core/dialog_search_replace.cpp create mode 100644 core/dialog_search_replace.h create mode 100644 core/dialog_selection.cpp create mode 100644 core/dialog_selection.h create mode 100644 core/dialog_shift_times.cpp create mode 100644 core/dialog_shift_times.h create mode 100644 core/dialog_spellcheck.cpp create mode 100644 core/dialog_spellcheck.h create mode 100644 core/dialog_style_editor.cpp create mode 100644 core/dialog_style_editor.h create mode 100644 core/dialog_style_manager.cpp create mode 100644 core/dialog_style_manager.h create mode 100644 core/dialog_styling_assistant.cpp create mode 100644 core/dialog_styling_assistant.h create mode 100644 core/dialog_timing_processor.cpp create mode 100644 core/dialog_timing_processor.h create mode 100644 core/dialog_translation.cpp create mode 100644 core/dialog_translation.h create mode 100644 core/drop.cpp create mode 100644 core/drop.h create mode 100644 core/export_clean_info.cpp create mode 100644 core/export_clean_info.h create mode 100644 core/export_fixstyle.cpp create mode 100644 core/export_fixstyle.h create mode 100644 core/export_framerate.cpp create mode 100644 core/export_framerate.h create mode 100644 core/fft.cpp create mode 100644 core/fft.h create mode 100644 core/fonts_collector.cpp create mode 100644 core/fonts_collector.h create mode 100644 core/frame_main.cpp create mode 100644 core/frame_main.h create mode 100644 core/frame_main_events.cpp create mode 100644 core/hilimod_textctrl.cpp create mode 100644 core/hilimod_textctrl.h create mode 100644 core/hotkeys.cpp create mode 100644 core/hotkeys.h create mode 100644 core/main.cpp create mode 100644 core/main.h create mode 100644 core/options.cpp create mode 100644 core/options.h create mode 100644 core/readme.txt create mode 100644 core/res.rc create mode 100644 core/splash.cpp create mode 100644 core/splash.h create mode 100644 core/static_bmp.cpp create mode 100644 core/static_bmp.h create mode 100644 core/string_codec.cpp create mode 100644 core/string_codec.h create mode 100644 core/subs_edit_box.cpp create mode 100644 core/subs_edit_box.h create mode 100644 core/subs_grid.cpp create mode 100644 core/subs_grid.h create mode 100644 core/text_file_reader.cpp create mode 100644 core/text_file_reader.h create mode 100644 core/text_file_writer.cpp create mode 100644 core/text_file_writer.h create mode 100644 core/timeedit_ctrl.cpp create mode 100644 core/timeedit_ctrl.h create mode 100644 core/tip.cpp create mode 100644 core/tip.h create mode 100644 core/todo.txt create mode 100644 core/toggle_bitmap.cpp create mode 100644 core/toggle_bitmap.h create mode 100644 core/utils.cpp create mode 100644 core/utils.h create mode 100644 core/validators.cpp create mode 100644 core/validators.h create mode 100644 core/variable_data.cpp create mode 100644 core/variable_data.h create mode 100644 core/version.h create mode 100644 core/vfr.cpp create mode 100644 core/vfr.h create mode 100644 core/vfw_wrap.cpp create mode 100644 core/vfw_wrap.h create mode 100644 core/video_box.cpp create mode 100644 core/video_box.h create mode 100644 core/video_display.cpp create mode 100644 core/video_display.h create mode 100644 core/video_slider.cpp create mode 100644 core/video_slider.h create mode 100644 core/video_zoom.cpp create mode 100644 core/video_zoom.h create mode 100644 core/yatta_wrap.cpp create mode 100644 core/yatta_wrap.h create mode 100644 docs/Bezier.png create mode 100644 docs/aegisub_styling_assistant.png create mode 100644 docs/aegisub_subtitle_field_colors.png create mode 100644 docs/aegisub_subtitle_hidden_tags.png create mode 100644 docs/aegisub_subtitle_not_hidden_tags.png create mode 100644 docs/anamorphic.png create mode 100644 docs/audiohigh.png create mode 100644 docs/audiomode.png create mode 100644 docs/audiomode_det.png create mode 100644 docs/audioview-basic.png create mode 100644 docs/audioview-karaoke-1.png create mode 100644 docs/audioview-karaoke-2.png create mode 100644 docs/audioview-split-dialogue.png create mode 100644 docs/audioview-timing.png create mode 100644 docs/automation-demo-6.png create mode 100644 docs/automation-demo-7.jpg create mode 100644 docs/automation-export-config.png create mode 100644 docs/automation-manager.png create mode 100644 docs/automation-script-lps.png create mode 100644 docs/button_bold.png create mode 100644 docs/button_closedbook.gif create mode 100644 docs/button_fonts.png create mode 100644 docs/button_italics.png create mode 100644 docs/button_main.gif create mode 100644 docs/button_main_d.gif create mode 100644 docs/button_main_h.gif create mode 100644 docs/button_next.gif create mode 100644 docs/button_next_d.gif create mode 100644 docs/button_next_h.gif create mode 100644 docs/button_openbook.gif create mode 100644 docs/button_outline_color.png create mode 100644 docs/button_prev.gif create mode 100644 docs/button_prev_d.gif create mode 100644 docs/button_prev_h.gif create mode 100644 docs/button_primary_color.png create mode 100644 docs/button_secondary_color.png create mode 100644 docs/button_shadow_color.png create mode 100644 docs/button_strikeout.png create mode 100644 docs/button_topic.gif create mode 100644 docs/button_underline.png create mode 100644 docs/clip_sample01.jpg create mode 100644 docs/color-picker.png create mode 100644 docs/example_button_v1.png create mode 100644 docs/example_button_v2.png create mode 100644 docs/example_button_v3.png create mode 100644 docs/feature comparison.xls create mode 100644 docs/fonts_collector_01.png create mode 100644 docs/fonts_collector_02.png create mode 100644 docs/fr_sample01.jpg create mode 100644 docs/fr_sample02.jpg create mode 100644 docs/fr_sample03.jpg create mode 100644 docs/help.hm3 create mode 100644 docs/jump.png create mode 100644 docs/karmode.png create mode 100644 docs/main_window_subtitles.png create mode 100644 docs/nonscroll.js create mode 100644 docs/pos_sample01.jpg create mode 100644 docs/pos_sample02.jpg create mode 100644 docs/pos_sample03.jpg create mode 100644 docs/right_click_menu_subtitle_field.png create mode 100644 docs/seek.png create mode 100644 docs/splash.PNG create mode 100644 docs/style_manager_1.png create mode 100644 docs/style_manager_2.png create mode 100644 docs/styles_multicolor_1.jpg create mode 100644 docs/styles_multicolor_2.jpg create mode 100644 docs/styles_multicolor_aegisub_1.png create mode 100644 docs/styles_multicolor_aegisub_2.png create mode 100644 docs/styles_standard.jpg create mode 100644 docs/styles_standard_aegisub.png create mode 100644 docs/styling_assistant.png create mode 100644 docs/styling_assistant_autocomplete_v1.png create mode 100644 docs/styling_assistant_autocomplete_v2.png create mode 100644 docs/subtitle_basics_Make_times_continous.png create mode 100644 docs/subtitle_basics_Recombine_v1.png create mode 100644 docs/subtitle_basics_Recombine_v2.png create mode 100644 docs/subtitle_basics_Recombine_v3.png create mode 100644 docs/subtitle_basics_join_as_karaoke.png create mode 100644 docs/subtitle_basics_join_concatenate.png create mode 100644 docs/subtitle_basics_join_keep_first.png create mode 100644 docs/subtitle_basics_swap.png create mode 100644 docs/subtitle_display_window.png create mode 100644 docs/subtitle_editing_window.png create mode 100644 docs/subtitle_options.png create mode 100644 docs/translation.png create mode 100644 docs/typeset_01.jpg create mode 100644 docs/typeset_02.jpg create mode 100644 docs/typeset_03.jpg create mode 100644 docs/typeset_04.jpg create mode 100644 docs/typeset_05.jpg create mode 100644 docs/typeset_06.jpg create mode 100644 docs/typeset_07.jpg create mode 100644 docs/typeset_example01.jpg create mode 100644 docs/typeset_example02.jpg create mode 100644 docs/typeset_example03.jpg create mode 100644 docs/typeset_example04.jpg create mode 100644 docs/vfr.png create mode 100644 docs/videomode.png create mode 100644 docs/zoom.png create mode 100644 installer/aegisub.nsi create mode 100644 locale/de/aegisub.mo create mode 100644 locale/de/aegisub.po create mode 100644 locale/de/wxstd.mo create mode 100644 locale/fr/aegisub.mo create mode 100644 locale/fr/aegisub.po create mode 100644 locale/fr/wxstd.mo create mode 100644 locale/pt_BR/aegisub.mo create mode 100644 locale/pt_BR/aegisub.po create mode 100644 locale/pt_BR/wxstd.mo create mode 100644 locale/ru/aegisub.mo create mode 100644 locale/ru/aegisub.po create mode 100644 locale/ru/wxstd.mo diff --git a/automation/automation-lua.txt b/automation/automation-lua.txt new file mode 100644 index 000000000..b4c63c88a --- /dev/null +++ b/automation/automation-lua.txt @@ -0,0 +1,548 @@ +Aegisub Automation documentation +Version 3 +Copyright 2005 Niels Martin Hansen. + +--- + +This document describes version 3 of the automation system used in Aegisub. +The automation system uses the Lua language for scripting engine. +See for more information. + +--- + +What is Automation? + +Aegisub Automation is a scripting system designed to automate many processing +tasks of ASS subtitles, instead of using tedious, error-prone manual +processing. The primary purpose is creating karaoke effects for anime fansubs. + +The Automation script is given the complete subtitle data from a subtitle +file, in a format suited for creating special effects. +The script will return a complete substiture for the original subtitle +data, allowing full freedom of processing. + +A number of helper functions are provided, to aid in finding errors in +scripts, as well as retrieve further data about the subtitles, needed to +created advanced effects. + +--- + +Scripts, files, functions: + +A script is a file containing Lua code. One file can define just one script, +but several scripts can share code by the help of including other files. + +All scripts are run in a separate interpreter, and as such don't have any way +of interacting with other loaded scripts. + +All strings in a script should be in UTF-8 encoding, without byte-order mark. +All strings input to a script are encoded as UTF-8. +Script files may start with an UTF-8 BOM (byte-order mark) or not, but this +is currently not well tested. + +A script must define certain global variables: + +version + Number. Version of the scripting interface used. + The version described in this file is 3. + To comply with version 3, version must be: 3 <= version < 4 +kind + String. Not used, but mandatory. Set it to "basic_ass" for now. +name + String. Displayed name of the script. +description + String. Optional. Long description of the script. +process_lines + Function. The main script function. +configuration + Table. Optional. Configuration options for the script. + +The functions are described in detail in the following. + +The script may define further global variables, but they do not have any +special meaning. Be aware, however, that later versions of the scripting +system might define further global variables with special meanings, so be +careful choosing names for private use globals. +It's recommended to prefix private global variables with "p_"; the scripting +system will never assign special meanings to global variables with that +prefix. + +The scripting system defines a global variable with name "aegisub", which +contains important values. You should not hide the "aegisub" variable. + +--- + +The processing function: + +The processing function is the heart of the script. + +It takes as input some meta-information about the subtitles, the styles +used in the subtitles, as well as the actual subtitle data to process. + +The output is a set of subtitle data in the same format as the input. +The output subtitle data will be used as a complete replacement of the +input data. +Future versions might allow modifying style data and meta data as well. + + +The processing function is defined as follows: + +function process_lines(meta, styles, lines, config) + +The arguments are: + +@meta + Table. Meta information about the script. (Script Info section.) +@styles + Table. Style definitions. (V4+ Styles section.) +@lines + Table. Subtitle events. (Events section.) +@config + Table. Values set for the configuration options provided. If no + configuration options were provided, this will be an empty table. + +Returns: One value. +This value must be a table, using the same format as @lines. +Note that the indexes in the return value may be either zero-based or +one-based, to allow for greater compatibility. You are encouraged to +use one-based indexes. + + +Description of @meta: + +This is a table with the following keys: + +res_x + Horizontal resolution of the script. +res_y + Vertical resolution of the script. + + +Description of @styles: + +This is a table with the following keys: + +-1 + Number. The amount of styles defined, called "n". +0 -> n-1 + Table. The actual style definitions. + + Table. The style definition with the specified name. + +The key -1 is used for count rather than "n", since one might have a style +definition with the name "n". + +A style definition is a table with the following keys: + +name + String. Name of the style. +fontname + String. Name of the font used. +fontsize + Number. Size of the font used. +color1 + String. Primary color. + All color fields use raw hexadecimal format, that is, no special characters + before or after the hex string. +color2 + String. Secondary color. +color3 + String. Outline color. +color4 + String. Shadow color. +bold + Boolean. Bold text or not. +italic + Boolean. Italic text or not. +underline + Boolean. Underlined text or not. +strikeout + Boolean. Striked-out text or not. +scale_x + Number. Horizontal scale. +scale_y + Number. Vertical scale. +spacing + Number. Spacing between characters. +angle + Number. Rotation angle in degrees. +borderstyle + Number. 1=Outline + drop shadow, 3=Opaque box (not really used???) +outline + Number. Thickness of outline. +shadow + Number. Distance of shadow from text. +align + Number. Numpad style alignment. +margin_l + Number. Left margin in pixels. +margin_r + Number. Right margin in pixels. +margin_v + Number. Vertical margin in pixels. +encoding + Number. Font encoding used. + + +Description of @lines: + +This is a table with the following keys: + +n + Number. The amount of lines. +0 -> n-1 + Table. The actual lines. + +A line is a table with the following key: + +kind + String. Can be "blank", "scomment", "comment" or "dialogue". + +The keys otherwise defined depends on the kind of the line. + +If the kind if "blank", no further fields are defined. + +If the kind is "scomment", the line is a "semicolon comment", and the +following key is defined: + +text + String. Text following the semicolon until end of line. EOL not included. + +If the kind is "comment" or "dialogue", the line is either a Comment: or +a Dialogue: line. In both cases, the following keys are defined: + +layer + Number. +start_time + Number. Start time of line in centiseconds. + (Might change to userdata later.) +end_time + Number. End time of line in centiseconds. + (Might change to userdata later.) +style + String. Style used for this line. +name + String. Character name speaking this line. +margin_l + Number. Left margin override, in pixels. (0=no override) +margin_r + Number. Right margin override, in pixels. (0=no override) +margin_v + Number. Right margin override, in pixels. (0=no override) +effect + String. Effect to apply to the line. (No error checking done.) +text + String. Text to display. +text_stripped + String. Same as text, but stripped for all tags, and newline/hardspace + tags are converted to real newlines/spaces. Non-hard spaces at the start/ + end of lines are stripped. +karaoke + Table. Line split into karaoke syllables. See below for more information. + +Note about output: +Neither text_stripped nor karaoke are used when the results are parsed, they +are only passed to simplify processing. You should set text to the final text +of the line, you want in the output. +It is encouraged to entirely leave text_stripped and karaoke out of the +tables in the result. + + +Karaoke tables: + +A karaoke table has a number of values indexed by numbers. Each value +represents a karaoke syllable. +Key "n" holds the number of syllables. The syllables can be accessed from +index 0 and up. The syllables are indexed chronologically. +A karaoke table always has at least one syllable. The first syllable (index +0) contains all data before the first timed syllable. +Each syllable is a table containing the following keys: + +duration + Number. Duration of the syllable in centiseconds. Always 0 for first + syllable. +kind + String. "Kind" of the karaoke, the name of the tag. For a \k type syllable, + kind is "k", for a \kf syllable kind is "kf". Freeform tags can be used, as + long as they start with the letter "k" or "K". + Always the empty string ("") for the first syllable. +text + String. Text of the syllable. This includes formatting tags. + For the first syllable, this contains everything before the first karaoke + timing tag. +text_stripped + String. Same as text, but with all formatting tags stripped. + + +Description of @config: + +This is a table. The keys are the names for the options defined in the global +"configuration" table. The values are the values provided by the user. + +--- + +Script configuration: + +An automation script can provide a configuration set, allowing the user to +set certain options before the script is called. + +This is performed through the "configuration" value. + +Scripts can define configuration options of the following types: + +label + A static, non-editable text displayed to the user. (Useful for adding + additional explanations for some options.) +text + Freeform text entry. +int + Integer numbers. A range of valid values can be specified. +float + Any kind of number. A range of valid values can be specified. +bool + A boolean on/off value. +colour + An RGB colour value. +style + The name of a style defined in the subtitles. + + +The "configuration" table: + +The "configuration" table contains a number of values indexed by numbers. +Each value defines a configuration option. +The configuration options must be in keys numbered from 1 to n, where n +is the number of options. No "n" key is required. +The configuration options will be presented to the user in the order defined. +Each configuration option is a table containing the following keys: + +name + String. The internal name used to refer to the configuration option. + Must not contain the colon or pipe characters. (ASCII 58 and 124.) +kind + String. One of "label", "text", "int", "float", "bool", "colour" and + "style". Defines what kind of option this is. +label + String. Name of the option, presented to the user. Should be very short. +hint + String. Longer description of the option, presented to the user as a + tooltip. Ignored for "label" kind options. +min + Number. Optional. Lowest value allowed. Only used for "int" and "float" kinds. +max + Number. Optional. Highest value allowed. Only used for "int" and "float" kinds. +default. + Type depends on "kind". The value given to this configuration option before + the user has entered another value. Ignored for "label" kind options. + +Data types for the different kinds: + +label + None. A label doesn't have a value, and won't be present in the @config + table in the process_lines function. +text + String. You might want to do some kind of extra validation on text input, as + it might be anything. +int + Number. Guaranteed to always be integer. +float + Number. Can be integer or not. +bool + Boolean. +colour + String. An ASS hex colourcode in "&HBBGGRR&" format. +style + String. The name of the style. The style can't be guaranteed to exist, as + another export filter in Aegisub might have removed it before your script + gets to run. + +--- + +Script environment and registration: + +A script is assigned to a subtitle file by adding it to the +"Automation Scripts" extra header in the [Script Info] section. This header +contains a list of script filenames, separated by pipe characters. Example: + + Automation Scripts: test1.lua|test2.lua + +All scripts run in their own separate interpreter. This means there is no +risk of name collisions, though also that scripts can't easily share code. + +If you need to share code between several scripts, you should create a +subdirectory to the script directory, and place include files there. + + +The settings for the configuration options for a script are stored in the ASS +file in the following way: + +Each script gets one line for configuration, named "Automation Settings" plus +a space plus the filename of the script. The filename used is stripped of all +path specifiers. (Use unique filenames for your scripts!) + +The value of the line is a pipe-separated list of "name:value" pairs. The name +is the internal name given by the "name" key. It is not mangled in any way. + +The way the value is stored depends on the kind of the option. + +label + Not stored. +text + The string is stored in an URL-encoding like manner. Some unsafe characters + are replaced with escape-sequences of the form #xx, where xx is a two-digit + hexadecimal number for the ASCII code of the escaped character. Only ASCII- + characters can be escaped this way, Unicode characters aren't supported. +int + Stored in ASCII base 10 without any group separators. +float + Stored in exponential notation, using ASCII base 10. (As the %e sprintf() + argument.) +bool + True is stored as "1", false as "0". +colour + Stored as in ASS hex format without any mangling. +style + Stored in the same manner as "text" kind options. + +--- + +Helper functions: + +There is a gloabl variable names "aegisub". This is a table containing +various helper functions. + +The following helper functions are defined: + + +function aegisub.set_status(text) + +Sets the current status-message. (Used for progress-reporting.) + +@text + String. The status message. + +Returns: nothing. + + +function aegisub.output_debug(text) + +Output text to a debug console. + +@text + String. The text to output. + +Returns: nothing. + + +function aegisub.colorstring_to_rgb(colorstring) + +Convert an ASS color-string to a set of RGB values. + +@colorstring + String. The color-string to convert. + +Returns: Four values, all numbers, being the color components in this +order: Red, Green, Blue, Alpha-channel + + +function aegisub.report_progress(percent) + +Report the progress of the processing. + +@percent + Number. How much of the data have been processed so far. (Percent) + +Returns: nothing. + + +function aegisub.text_extents(style, text) + +Calculate the on-screen pixel size of the given text using the given style. + +@style + Table. A single style definition like those passed to process_lines. +@text + String. The text to calculate the extents for. This should not contain + formatting codes, as they will be treated as part of the text. + +Returns 4 values: +1: Number. Width of the text, in pixels. +2: Number. Height of the text, in pixels. +3: Number. Descent of the text, in pixels. +4: Number. External leading for the text, in pixels. + +Short description of the values returned: +Width: The X advance of the text, how much the "cursor" moves forward when +this text is rendered. +Height: The total height of the text, including internal leading. +Descent: How far below the baseline a character can extend. The ascent of +the text can be calculated as (height - descent). +External leading: How much vertical spacing will be added between the lines +of text rendered with this font. The total height of a line is +(height + external_leading). + + +function aegisub.frame_from_ms(ms) + +Return the video frame-number for the given time. + +@ms + Number. Time in miliseconds to get the frame number for. + +Returns: A number, the frame numer. If there is no framerate data, returns +nil. + + +function aegisub.ms_from_frame(frame) + +Returns the start-time for the given video frame-number. + +@frame + Number. Frame-number to get start-time from. + +Returns: A number, the start-time of the frame. If there is no framerate +data, returns nil. + + +function include(filename) + +Include the named script. The script search-path defined in Aegisub will be +used, searching for the script. +If the filename is relative, the regular search path will not be used, but +instead the filename will be taken as relative to the directory the current +script is located in. +Note that if you use include() inside an included script, relative paths +will still be taken relative to the original script, and not relative to the +current included script. This is a design limitation. +The included script is loaded as an anonymous function, which is executed in +the current environment. This has two implications: You can include files +based on conditional statements, and even in loops, and included files can +return values using the "return" statement. + +@filename + String. Name of the file to include. + +Returns: Depends on the script included. + +Note that if the file couldn't be found, the script will be terminated +(or fail to load.) + +--- + +Versions of the scripting interface: + +Here's a quick history of the scripting interface: + +Version 1 + Using Lua as engine. + The scripts used in the Karaoke Effector application, avaible at: + + +Version 2 + Using Python as engine. + The first draft for an Aegisub automation engine. + Never implemented. + +Version 3 + Using Lua as engine. + The current version. diff --git a/automation/demos/1-minimal.lua b/automation/demos/1-minimal.lua new file mode 100644 index 000000000..837ee35bb --- /dev/null +++ b/automation/demos/1-minimal.lua @@ -0,0 +1,16 @@ +-- Aegisub Automation demonstration script +-- Original written by Niels Martin Hansen +-- Given into the public domain + +version = 3 +kind = "basic_ass" +name = "Minimal demonstration" +description = "A very minimal demonstration of the strucrure of an Automation script." +configuration = {} + +function process_lines(meta, styles, lines, config) + aegisub.report_progress(50) + aegisub.output_debug("Test script 1 running") + aegisub.report_progress(100) + return lines +end diff --git a/automation/demos/2-dump.lua b/automation/demos/2-dump.lua new file mode 100644 index 000000000..a52b284f1 --- /dev/null +++ b/automation/demos/2-dump.lua @@ -0,0 +1,43 @@ +-- Aegisub Automation demonstration script +-- Original written by Niels Martin Hansen +-- Given into the public domain + +version = 3 +kind = "basic_ass" +name = "Reading data demonstration" +description = "This is a demonstration of how to access the various data passed to an Automation script. It loops over the data structures provided, and dumps them to the debug console." +configuration = {} + +function process_lines(meta, styles, lines, config) + out = aegisub.output_debug + + out(string.format("Metadata: res_x=%d res_s=%d", meta.res_x, meta.res_y)) + + numstyles = styles[-1] + out("Number of styles: " .. numstyles) + for i = 0, numstyles-1 do + out(string.format("Style %d: name='%s' fontname='%s'", i, styles[i].name, styles[i].fontname)) + end + + out("Number of subtitle lines: " .. lines.n) + for i = 0, lines.n-1 do + aegisub.report_progress(i/lines.n*100) + if lines[i].kind == "dialogue" then + out(string.format("Line %d: dialogue start=%d end=%d style=%s", i, lines[i].start_time, lines[i].end_time, lines[i].style)) + out(" Text: " .. lines[i].text) + out(" Stripped text: " .. lines[i].text_stripped) + out(" Number of karaoke syllables: " .. lines[i].karaoke.n) + for j = 0, lines[i].karaoke.n-1 do + syl = lines[i].karaoke[j] + extx, exty, extd, extl = aegisub.text_extents(styles[lines[i].style], syl.text_stripped) + out(string.format(" Syllable %d: dur=%d kind=%s text='%s' text_stripped='%s' extx=%d exty=%d extd=%d extl=%d", j, syl.duration, syl.kind, syl.text, syl.text_stripped, extx, exty, extd, extl)) + --out(string.format(" Syllable %d: kind=%s", j, syl.kind)) + end + else + out(string.format("Line %d: %s", i, lines[i].kind)) + end + end + + -- but really just do nothing + return lines +end diff --git a/automation/demos/3-include.lua b/automation/demos/3-include.lua new file mode 100644 index 000000000..889b5246d --- /dev/null +++ b/automation/demos/3-include.lua @@ -0,0 +1,16 @@ +-- Aegisub Automation demonstration script +-- Original written by Niels Martin Hansen +-- Given into the public domain + +include("utils.lua") + +name = "Include demo" +description = "Simple demonstration of the include function." + +version, kind, configuration = 3, 'basic_ass', {} + +function process_lines(meta, styles, lines, config) + lines[lines.n] = copy_line(lines[0]) + lines.n = lines.n + 1 + return lines +end diff --git a/automation/demos/4-text_extents.lua b/automation/demos/4-text_extents.lua new file mode 100644 index 000000000..73f3e41b5 --- /dev/null +++ b/automation/demos/4-text_extents.lua @@ -0,0 +1,55 @@ +-- Aegisub Automation demonstration script +-- Original written by Niels Martin Hansen +-- Given into the public domain + +include("utils.lua") + +name = "Text placement demo" +description = "Demonstration of the text_extents function, to do per-syllable placement of text." + +version, kind, configuration = 3, 'basic_ass', {} + +function process_lines(meta, styles, lines, config) + -- Prepare local variables + local output = { n=0 } + -- Loop through every line + for i = 0, lines.n-1 do + aegisub.report_progress(i/lines.n*100) + -- Only process dialogue lines + if lines[i].kind ~= "dialogue" then + table.insert(output, lines[i]) + else + -- This is just for making the code a bit easier to read + local line = lines[i] + -- Get the rendered size of the entire line. (Won't work if there's line breaks in it.) + local totalx, totaly = aegisub.text_extents(styles[line.style], line.text_stripped) + -- Calculate where the first syllable should be positioned, if the line is to appear centered on screen + local curx, cury = (meta.res_x - totalx) / 2, meta.res_y / 2 + -- And more preparations for per-syllable placement + local startx = curx + local tstart, tend = 0, 0 + -- Now process each stllable + for j = 1, line.karaoke.n-1 do + -- A shortcut variable, and, most important: a copy of the original line + local syl, syllin = line.karaoke[j], copy_line(line) + -- Calculate the ending time of this syllable + tend = tstart + syl.duration*10 + -- Get the rendered size of this syllable + local extx, exty, extd, extl = aegisub.text_extents(styles[line.style], syl.text_stripped) + -- Some debug stuff... + aegisub.output_debug(string.format("text_extents returned: %d, %d, %d, %d", extx, exty, extd, extl)); + -- Replace the text of the copy of the line with this syllable, moving around + syllin.text = string.format("{\\an4\\move(%d,%d,%d,%d,%d,%d)\\kf%d\\kf%d}%s", curx, cury, curx, cury-exty, tstart, tend, tstart/10, syl.duration, syl.text) + -- Add the line to the output + table.insert(output, syllin) + -- And prepare for next iteration + curx = curx + extx + tstart = tend + end + -- More debug stuff + aegisub.output_debug(string.format("after syllable loop: totalx=%d curx-startx=%d", totalx, curx-startx)) + end + end + -- And remember to return something :) + return output +end diff --git a/automation/demos/5-configuration.lua b/automation/demos/5-configuration.lua new file mode 100644 index 000000000..aa54e43bd --- /dev/null +++ b/automation/demos/5-configuration.lua @@ -0,0 +1,72 @@ +-- Aegisub Automation demonstration script +-- Original written by Niels Martin Hansen +-- Given into the public domain + +name = "Configuration demo" +description = "This script allows the user to input some data in the Aegisub Export window. Some of these data are used during the processing, to change the subtitles. (The strings is prepended every dialogue line.)" +configuration = { + [1] = { + name = "thelabel"; + kind = "label"; + label = "This is a label control. Just for shows."; + hint = "Tooltip for label?!?"; + }; + [2] = { + name = "thestring"; + kind = "text"; + label = "String:"; + hint = "The string to insert at the beginning of each line"; + default = "foobar " + }; + [3] = { + name = "theint"; + kind = "int"; + label = "Integer:"; + hint = "An integer number to display in debug output"; + default = 50; + min = 0; + max = 100; + }; + [4] = { + name = "thefloat"; + kind = "float"; + label = "Float number:"; + hint = "Just a random float number"; + default = 3.1415927; + }; + [5] = { + name = "thebool"; + kind = "bool"; + label = "I accept"; + hint = "Check if you accept the terms of the license agreement"; + default = false; + }; + [6] = { + name = "thecolour"; + kind = "colour"; + label = "Favourite color:"; + hint = "What color do you want your pantsu?"; + default = "&H8080FF"; + }; + [7] = { + name = "thestyle"; + kind = "style"; + label = "Style:"; + hint = "Pick a style the effects will apply to, or none to apply to everything"; + default = ""; + } +} + +version, kind = 3, 'basic_ass' + +function process_lines(meta, styles, lines, config) + aegisub.output_debug("The string entered is: " .. config.thestring) + for i = 0, lines.n-1 do + aegisub.report_progress(i/lines.n*100) + if lines[i].kind == "dialogue" then + lines[i].text = config.thestring .. lines[i].text + end + end + return lines +end + diff --git a/automation/demos/6-simple-effect.lua b/automation/demos/6-simple-effect.lua new file mode 100644 index 000000000..422e2eba5 --- /dev/null +++ b/automation/demos/6-simple-effect.lua @@ -0,0 +1,87 @@ +-- Aegisub Automation demonstration script +-- Original written by Niels Martin Hansen +-- Given into the public domain + +-- Define some required constants +-- version and kind are required to have the given values for the script to work. +-- configuration is not needed in this script, so it's just left as an empty table +version, kind, configuration = 3, "basic_ass", {} + +-- Define the displayed name of the script +name = "Simple karaoke effect" +-- A longer description of the script +description = "A simple karaoke effect, with the source code heavily commented. Provided as a starting point for a useful effect." + +-- The actual script function +function process_lines(meta, styles, lines, config) + -- Create a local variable to store the output subtitles in + local output = { n=0 } + + -- Start to loop over the lines, one by one + -- The lines are numbered 0..n-1 + for i = 0, lines.n-1 do + -- Show the user how far the script has got + aegisub.report_progress(i/lines.n*100) + -- First check if the line is even a dialogue line. If it's not, no need to process it. + if lines[i].kind ~= "dialogue" then + table.insert(output, lines[i]) + else + -- This is a dialogue line, so process is + -- Make a nicer name for the line we're processing + newline = lines[i] + -- Also show the line to the user + aegisub.set_status(newline.text_stripped) + + -- The text of the new line will be build little by little + -- Each line has 700 ms fadein, 300 ms fadeout, + -- is positioned at the center of the screen (\an8) + -- and the highlighting should be delayed by 1000 ms (100 cs) + newline.text = string.format("{\\fad(700,300)\\pos(%d,30)\\k100}", meta.res_x/2) + -- Make the line start 1000 ms (100 cs) earlier than original + newline.start_time = newline.start_time - 100 + + -- Now it's time to loop through the syllables one by one, processing them + -- The first syllable is usually a "null" syllable, not containing real data, so that one should be skipped. + -- This variable is used to keep track of when the last syllable ended + -- It's initialised to 1000, since the start of the line was pushed 1000 ms back + local cursylpos = 1000 + for j = 1, lines[i].karaoke.n-1 do + local syl = lines[i].karaoke[j] + -- Call another function to process the syllable + newline.text = newline.text .. doSyllable(syl.text, cursylpos, cursylpos+syl.duration*10, syl.duration, syl.kind) + -- Calculate the start time of the next syllable + cursylpos = cursylpos + syl.duration*10 + end + + -- The entire line has been calculated + -- Add it to the output + table.insert(output, newline) + end + end + + -- All lines processed, and output filled + -- Just return it + -- (This is important! If you don't return anything, the output file will be empty!) + return output +end + +-- This effect was originally written in the "Effector" program, which can be considered the first version of Automation. +-- This following function is almost verbatimly copied from that original script. +-- This is done in order to show how you can make sub-functions to make your script more readable. +-- The contents of this function could also just be pasted into the middle of the main loop in process_lines, +-- but that generally makes scripts harder to read. +function doSyllable(text, t_start, t_end, t_dur, ktype) + -- Declare two local variables needed here + -- (If they're not declared local, they will be global.) + local a, b + -- If it's a "long" syllable, let the effect be different + if t_dur > 75 then + a = t_start + 500 + b = t_end + else + a = t_start + 100 + b = t_start + 500 + end + -- Return the replacement for the syllable, including some ASS tags for format it + return string.format("{\\r\\t(%d,%d,\\1c&H808080&\\2c&H808080&)\\kf%d}%s", a, b, t_dur, text) +end diff --git a/automation/demos/7-advanced-effect.lua b/automation/demos/7-advanced-effect.lua new file mode 100644 index 000000000..e01bf3166 --- /dev/null +++ b/automation/demos/7-advanced-effect.lua @@ -0,0 +1,189 @@ +-- Aegisub Automation demonstration script +-- Original written by Niels Martin Hansen +-- Given into the public domain +-- But still, please don't use the effect generated by this script +-- (unchanged) for your own works. + +include("utils.lua") + +name = "Advanced karaoke effect" +description = "An advanced karaoke effect, making heavy use of both line-copying and per-syllable text placement. Also demonstrates how to treat lines differently, based on their style, and syllables differently based on the timing tag used." +version, kind, configuration = 3, 'basic_ass', {} + +function process_lines(meta, styles, lines, config) + local output = { n=0 } + math.randomseed(5922) -- just to make sure it's initialised the same every time + for curline = 0, lines.n-1 do + aegisub.report_progress(curline/lines.n*100) + local lin = lines[curline] + if lin.kind == "dialogue" and lin.style == "op romaji" then + doromaji(lin, output, styles["op romaji"], 30) + elseif lin.kind == "dialogue" and lin.style == "op kanji" then + -- Just one of these lines should be uncommented. + -- This script was written before configuration worked, otherwise it would use configuration + -- to select which of the two effects to use for "op kanji" style lines. + --doromaji(lin, output, styles["op kanji"], 20) + dokanji(lin, output, styles["op kanji"]) + else + -- Unknown lines are copied verbatim + table.insert(output, lin) + end + end + return output +end + +function doromaji(lin, output, sty, linetop) + aegisub.set_status(lin.text_stripped) + + --local linetop = 50 + + -- prepare syllable data + local linewidth = 0 + local syltime = 0 + local syls = {n=0} + for i = 1, lin.karaoke.n-1 do + local syl = lin.karaoke[i] + syl.width, syl.height, syl.descent, syl.extlead = aegisub.text_extents(sty, syl.text_stripped) + syl.left = linewidth + syl.start_time = syltime + syl.end_time = syltime + syl.duration + syltime = syltime + syl.duration + if syl.kind == "kf" and (syl.text == " " or syl.text == " ") then + -- The font this effect was made to work with has too wide spaces, so make those half width + syl.width = math.floor(syl.width / 2) + syl.kind = "space" + elseif syl.kind == "kf" then + syl.kind = "deco" + elseif syl.kind == "k" then + syl.kind = "reg" + elseif syl.kind == "ko" then + syl.kind = "dance" + else + syl.kind = "ignore" + end + if syl.text == "#" then + syls[syls.n-1].duration = syls[syls.n-1].duration + syl.duration + syls[syls.n-1].end_time = syls[syls.n-1].end_time + syl.duration + else + linewidth = linewidth + syl.width + syls[syls.n] = syl + syls.n = syls.n + 1 + end + end + local lineofs = math.floor((640 - linewidth) / 2) + + for i = 0, syls.n-1 do + local syl = syls[i], copy_line(lin) + if syl.kind == "space" then + -- Spaces are skipped. Since the width of them is already incorporated in the position calculations + -- for the following syllables, they can safely be stripped from output, without losing the spaces. + elseif syl.kind == "ignore" then + -- These are actually syllable kinds we don't know what to do about. They are not included in the output. + else + local startx, starty, shakex, shakey, enterangle + -- angle to enter from + enterangle = math.rad((180 / lin.karaoke.n) * i - 90) + -- position to enter from (350 pixels from (320, 50)) + startx = math.sin(enterangle)*350 + 320 + starty = linetop - math.cos(enterangle)*350 + if syl.kind == "reg" then + shakex = (syl.left+lineofs) + math.random(-5, 5) + shakey = linetop + math.random(-5, 5) + elseif syl.kind == "deco" then + shakex, shakey = syl.left+lineofs, linetop + elseif syl.kind == "dance" then + shakex = (syl.left+lineofs) + math.random(-5, 5) + shakey = linetop + math.random(-5, 5) + end + -- origin for rotation + local orgx = syl.left + lineofs + syl.width/2 + local orgy = linetop + syl.height/2 + -- entry effect + local enterlin = copy_line(lin) + enterlin.start_time = lin.start_time - 40 + enterlin.end_time = lin.start_time + enterlin.text = string.format("{\\move(%d,%d,%d,%d)\\fr%d\\t(\\fr0)\\an7}%s", startx, starty, shakex, shakey, -math.deg(enterangle), syl.text) + table.insert(output, enterlin) + -- main highlight effect + local newlin = copy_line(lin) + local hilistart, hilimid, hiliend = syl.start_time*10, (syl.start_time+syl.duration/2)*10, (syl.start_time+syl.duration)*10 + newlin.text = string.format("\\move(%d,%d,%d,%d,%d,%d)\\an7}%s", shakex, shakey, syl.left+lineofs, linetop, hilistart, hiliend, syl.text) + newlin.layer = 1 + if syl.kind == "dance" then + local fx = string.format("\\org(%d,%d)\\t(%d,%d,\\fr30)\\t(%d,%d,\\fr-30)\\t(%d,%d,\\3c&H0000ff&)\\t(%d,%d,\\3c&H000080&)\\t(%d,%d,\\fr0)", orgx, orgy, hilistart, hilistart+20, hilistart+20, hiliend, hilistart, hilimid, hilimid, hiliend, hiliend, hiliend+syl.duration*10) + newlin.text = fx .. newlin.text + else + local fx = string.format("\\t(%d,%d,\\3c&H0000ff&)\\t(%d,%d,\\3c&H000080&)", hilistart, hilimid, hilimid, hiliend) + newlin.text = fx .. newlin.text + end + local bord = copy_line(newlin) + bord.layer = 0 + bord.text = "{" .. bord.text + newlin.text = "{\\bord0" .. newlin.text + table.insert(output, bord) + table.insert(output, newlin) + -- leave effect + -- cut the line over in two, lower half "drops down", upper just fades away + local tophalf = copy_line(lin) + tophalf.start_time = lin.end_time + tophalf.end_time = lin.end_time + 100 + tophalf.text = string.format("\\3c&H000080&\\fad(0,700)\\pos(%d,%d)\\an7}%s", syl.left+lineofs, linetop, syl.text_stripped) + local bottomhalf = copy_line(tophalf) + tophalf.text = string.format("{\\t(0,200,\\1c&H000080&)\\clip(0,0,640,%d)%s", linetop+syl.height/2, tophalf.text) + bottomhalf.text = string.format("{\\org(%d,%d)\\clip(0,%d,640,480)\\t(0,200,\\1c&H000080&)\\t(200,1000,1.2,\\frx90\\clip(0,%d,640,480)%s", 320, linetop+syl.height, linetop+syl.height/2, linetop+syl.height, bottomhalf.text) + table.insert(output, tophalf) + table.insert(output, bottomhalf) + end + end +end + +function dokanji(lin, output, sty) + aegisub.set_status(lin.text_stripped) + + local fontname = "@HGSHigemoji" + + local lineheight = 0 + local syltime = 0 + local syls = {n=0} + for i = 1, lin.karaoke.n-1 do + local syl = lin.karaoke[i] + syl.height, syl.width, syl.descent, syl.extlead = aegisub.text_extents(sty, syl.text_stripped) + syl.start_time = syltime + syl.end_time = syltime + syl.duration + syltime = syltime + syl.duration + if syl.text == "#" then + syls[syls.n-1].duration = syls[syls.n-1].duration + syl.duration + syls[syls.n-1].end_time = syls[syls.n-1].end_time + syl.duration + --elseif syl.text == "" then + -- skip + else + lineheight = lineheight + syl.height + syls[syls.n] = syl + syls.n = syls.n + 1 + end + end + + for i = 0, syls.n-1 do + local syl = syls[i] + local top = 170 + for j = 0, 8 do + if i+j >= syls.n then + break + end + local newlin = copy_line(lin) + newlin.start_time = lin.start_time + syl.start_time + newlin.end_time = newlin.start_time + syl.duration + local startalpha, targetalpha + if j == 0 then + startalpha = 0 + targetalpha = 255 + else + startalpha = j * 255 / 8 + targetalpha = (j-1) * 255 / 8 + end + newlin.text = string.format("{\\fn%s\\an7\\fr-90\\move(%d,%d,%d,%d)\\1a&H%2x&\\3a&H%2x&\\t(\\1a&H%2x&\\3a&H%2x&)}%s", fontname, 620, top, 620, top-syl.height, startalpha, startalpha, targetalpha, targetalpha, syls[i+j].text) + top = top + syls[i+j].height + table.insert(output, newlin) + end + end +end diff --git a/automation/demos/8-skeleton.lua b/automation/demos/8-skeleton.lua new file mode 100644 index 000000000..6675d35f8 --- /dev/null +++ b/automation/demos/8-skeleton.lua @@ -0,0 +1,18 @@ +-- Aegisub Automation demonstration script +-- Original written by Niels Martin Hansen +-- Given into the public domain + +name = "Karaoke skeleton demo" +description = "This script demonstrates the use of the karaskel.lua include file, to avoid writing almost identical code for every karaoke effect script." + +version, kind, configuration = 3, 'basic_ass', {} + +include("karaskel.lua") + +function do_syllable(meta, styles, config, line, syl) + if syl.i == 0 then + return syl.text + else + return string.format("{\\r\\k%d\\t(%d,%d,\\1c&H%s&)}%s", syl.duration, syl.start_time, syl.end_time, line.styleref.color2, syl.text) + end +end diff --git a/automation/demos/9-skeleton-advanced.lua b/automation/demos/9-skeleton-advanced.lua new file mode 100644 index 000000000..ac251500c --- /dev/null +++ b/automation/demos/9-skeleton-advanced.lua @@ -0,0 +1,49 @@ +-- Aegisub Automation demonstration script +-- Original written by Niels Martin Hansen +-- Given into the public domain + +name = "Advanced skeleton demo" +description = "This script demonstrates using the karaskel-adv.lua include file to make rceation of per-syllable positioning effects easier." + +version, kind, configuration = 3, 'basic_ass', {} + +include("karaskel-adv.lua") + +-- What kind of effect this makes: +-- Each syllable "jumps" up and down once during its duration +-- This is achieved using two \move operations, and as known, gabest's TextSub can only handle one \move per line +-- So we need two lines per syllable, split exactly in the middle of the duration of the syllable + +function do_syllable(meta, styles, config, line, syl) + -- Make two copies of the original line (having the right timings etc) + local half1, half2 = copy_line(line), copy_line(line) + -- Make the first half line end halfway into the duration of the syllable + half1.end_time = half1.start_time + syl.start_time/10 + syl.duration/2 + -- And make the second half line start where the first one ends + half2.start_time = half1.end_time + -- Where to move the syllable to/from + local fromx, fromy = line.centerleft+syl.center, line.height*2 + 20 + local tox, toy = fromx, fromy - 10 + -- Generate some text for the syllable + half1.text = string.format("{\\an8\\move(%d,%d,%d,%d,%d,%d)}%s", fromx, fromy, tox, toy, syl.start_time, syl.start_time+syl.duration*5, syl.text_stripped) + half2.text = string.format("{\\an8\\move(%d,%d,%d,%d,%d,%d)}%s", tox, toy, fromx, fromy, 0, syl.duration*5, syl.text_stripped) + -- Things will look bad with overlapping borders and stuff unless + -- we manually layer borders lower than text, + -- and shadows lower than borders, so let's do that + local half1b, half1s = copy_line(half1), copy_line(half1) + half1b.text = "{\\1a&HFF&\\shad0}" .. half1b.text + half1s.text = "{\\1a&HFF&\\bord0}" .. half1s.text + half1.text = "{\\bord0\\shad0}" .. half1.text + half1.layer = 2 + half1b.layer = 1 + half1s.layer = 0 + local half2b, half2s = copy_line(half2), copy_line(half2) + half2b.text = "{\\1a&HFF&\\shad0}" .. half2b.text + half2s.text = "{\\1a&HFF&\\bord0\\shad2}" .. half2s.text + half2.text = "{\\bord0\\shad0}" .. half2.text + half2.layer = 2 + half2b.layer = 1 + half2s.layer = 0 + -- Done, return the two new lines + return {n=6, [1]=half1, [2]=half2b, [3]=half1s, [4]=half2, [5]=half2b, [6]=half2s} +end diff --git a/automation/demos/readme.txt b/automation/demos/readme.txt new file mode 100644 index 000000000..16f078c2c --- /dev/null +++ b/automation/demos/readme.txt @@ -0,0 +1,18 @@ +This directory contains various Aegisub Automation scripts provided for demonstration +purposes. Most of these were originally written for testing the various functions +in Automation durings its development, but they hopefully can also serve as good +places to learn how to do things. +All of these scripts are written by Niels Martin Hansen. + +They are given into the public domain, but if you use a substantial part of any of +the more advanced ones, I'd really like a bit of credit where it's due. + +And remember, it's cheap to use someone else's advanced karaoke effect, especially +without any changes at all. It also gives bad karma. Like, *really* bad :) + + +WARNING! +DO NOT ADD YOUR OWN FILES TO THIS DIRECTORY, +they will be deleted when you uninstall/upgrade Aegisub. +This also means it's unwise to modify these files, if you want to keep your +changes to them. diff --git a/automation/factorybrew/line-per-syllable.lua b/automation/factorybrew/line-per-syllable.lua new file mode 100644 index 000000000..63e8afd04 --- /dev/null +++ b/automation/factorybrew/line-per-syllable.lua @@ -0,0 +1,158 @@ +--[[ + Copyright (c) 2005, Niels Martin Hansen + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Aegisub Group nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + ]] + +-- Aegisub Automation factory-brewed script +-- "Line-per-syllable effects" + +-- Use karaskel.lua for skeleton code +include("karaskel-adv.lua") + +-- Define the name of the script +name = "Line-per-syllable effects" +-- Define a description of the script +description = "Makes line-per-syllable effects, that is, each syllable in the script gets its own line. This allows for more advanced effects, such as syllables moving about separately, or overlapping each other." +-- Define the script variables that can be configured graphically +-- This is the primary point of the script, being able to configure it graphically +configuration = { + -- First a label to descript what special variables can be used + [1] = { + name = "label1"; + kind = "label"; + label = [[Variable-names are prefixed with $, +expressions are enclosed in % pairs. +Variables: + $START = Start-time of syllable (ms) + $END = End-time of syllable (ms) + $MID = Time midways through the syllable (ms) + $DUR = Duration of syllable (cs) + $X = X-position of syllable (center) + $WIDTH = width of syllable + $HEIGHT = height of syllable + (There is no $Y) +Calculation example: + \t($start,%$start+10,\fscx110\fscy110) + \move($x,40,%$x-$width+10%,40) +Remember you should always have a \pos or \move in your effect.]]; + hint = "" + -- No "default", since a label doesn't have a value + }, + -- Then a text field to input the string to replace \k's with + -- Make the default a "NOP" string + [2] = { + name = "k_repstr"; + kind = "text"; + label = "Effect"; + hint = "The string to place at the start of every syllable line."; + default = "{\\an5\\pos($X,40)\\t($START,$END,\\fry360)}" + }, + -- Allow the user to specify whether to strip tags or not + [3] = { + name = "striptags"; + kind = "bool"; + label = "Strip all tags"; + hint = "Strip all formatting tags apart from the processed karaoke tags?"; + default = false + }, + [4] = { + name = "workstyle"; + kind = "style"; + label = "Line style"; + hint = "Only apply the effect to lines with this style. Empty means apply to all lines."; + default = "" + } +} +-- Mandatory values +version, kind= 3, 'basic_ass' + +function do_syllable(meta, styles, config, line, syl) + -- text is the replacement text for the syllable + -- ktext is the karaoke effect string + local text, ktext + + -- Prepare the stripped or unstripped main text + if config.striptags then + text = syl.text_stripped + else + text = syl.text + end + + -- Don't bother with empty syllables + if syl.text == "" then + return { n=0 } + end + + -- Add the variable names to the syllable data + syl["dur"] = syl.duration + syl["start"] = syl.start_time + syl["end"] = syl.end_time + syl["mid"] = syl.start_time + syl.duration*5 + syl["x"] = syl.center + line.centerleft + + -- Prepare the karaoke effect string + ktext = config.k_repstr + + -- Function for replacing the variables + local function var_replacer(varname) + varname = string.lower(varname) + if syl[varname] ~= nil then + return syl[varname] + else + aegisub.output_debug(string.format("Unknown variable name: %s", varname)) + return "$" .. varname + end + end + -- Replace the variables in the ktext + ktext = string.gsub(ktext, "$(%a+)", var_replacer) + + -- Function for evaluating expressions + local function expression_evaluator(expression) + chunk, err = loadstring(string.format("return (%s)", expression)) + if (err) ~= nil then + aegisub.output_debug(string.format("Error parsing expression:\n%s", expression, err)) + return "%" .. expression .. "%" + else + return chunk() + end + end + -- Find and evaluate expressions + ktext = string.gsub(ktext, "%%([^%%]*)%%", expression_evaluator) + + local newline = copy_line(line) + newline.text = ktext .. text + + return { n=1, [1]=newline } +end + +function do_line(meta, styles, config, line) + if config.workstyle == "" or config.workstyle == line.style then + return adv_do_line(meta, styles, config, line) + else + return { n=1, [1]=line } + end +end diff --git a/automation/factorybrew/readme.txt b/automation/factorybrew/readme.txt new file mode 100644 index 000000000..2731941b3 --- /dev/null +++ b/automation/factorybrew/readme.txt @@ -0,0 +1,12 @@ +This directory contains flexible, configurable Automation scripts to do common jobs, +without having to do programming. + +You can load these scripts into an ASS file in Aegisub, and apply an effect without +doing more than writing ASS override tags. + + +WARNING! +DO NOT ADD YOUR OWN FILES TO THIS DIRECTORY, +they will be deleted when you uninstall/upgrade Aegisub. +This also means it's unwise to modify these files, if you want to keep your +changes to them. diff --git a/automation/factorybrew/simple-k-replacer.lua b/automation/factorybrew/simple-k-replacer.lua new file mode 100644 index 000000000..cb32bbba7 --- /dev/null +++ b/automation/factorybrew/simple-k-replacer.lua @@ -0,0 +1,143 @@ +--[[ + Copyright (c) 2005, Niels Martin Hansen + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Aegisub Group nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + ]] + +-- Aegisub Automation factory-brewed script +-- "Basic \k replacer" + +-- Use karaskel.lua for skeleton code +include("karaskel.lua") + +-- Define the name of the script +name = "Basic \\k replacer" +-- Define a description of the script +description = "Makes basic karaoke effects. Replace all \\k tags (and variations) with a custom string, where some variables can be substituted in." +-- Define the script variables that can be configured graphically +-- This is the primary point of the script, being able to configure it graphically +configuration = { + -- First a label to descript what special variables can be used + [1] = { + name = "label1"; + kind = "label"; + label = [[Variable-names are prefixed with $, +expressions are enclosed in % pairs. +Variables: + $START = Start-time of syllable (ms) + $END = End-time of syllable (ms) + $MID = Time midways through the syllable (ms) + $DUR = Duration of syllable (cs) +Calculation example: + \t($start,%$start+$dur*2%,\fscx110) + \t(%$start+$dur*2%,$end,\fscx90)]]; + hint = "" + -- No "default", since a label doesn't have a value + }, + -- Then a text field to input the string to replace \k's with + -- Make the default a "NOP" string + [2] = { + name = "k_repstr"; + kind = "text"; + label = "\\k replacement"; + hint = "The string to replace \\k tags with. Should start and end with { } characters."; + default = "{\\k$DUR}" + }, + -- Allow the user to specify whether to strip tags or not + [3] = { + name = "striptags"; + kind = "bool"; + label = "Strip all tags"; + hint = "Strip all formatting tags apart from the processed karaoke tags?"; + default = false + }, + [4] = { + name = "workstyle"; + kind = "style"; + label = "Line style"; + hint = "Only apply the effect to lines with this style. Empty means apply to all lines."; + default = "" + } +} +-- Mandatory values +version, kind= 3, 'basic_ass' + +function do_syllable(meta, styles, config, line, syl) + -- text is the replacement text for the syllable + -- ktext is the karaoke effect string + local text, ktext + + -- Prepare the stripped or unstripped main text + if config.striptags then + text = syl.text_stripped + else + text = syl.text + end + + -- Add the variable names to the syllable data + syl["dur"] = syl.duration + syl["start"] = syl.start_time + syl["end"] = syl.end_time + syl["mid"] = syl.start_time + syl.duration*5 + + ktext = config.k_repstr + + -- Function for replacing the variables + local function var_replacer(varname) + varname = string.lower(varname) + if syl[varname] ~= nil then + return syl[varname] + else + aegisub.output_debug(string.format("Unknown variable name: %s", varname)) + return "$" .. varname + end + end + -- Replace the variables in the ktext + ktext = string.gsub(ktext, "$(%a+)", var_replacer) + + -- Function for evaluating expressions + local function expression_evaluator(expression) + chunk, err = loadstring(string.format("return (%s)", expression)) + if (err) ~= nil then + aegisub.output_debug(string.format("Error parsing expression:\n%s", expression, err)) + return "%" .. expression .. "%" + else + return chunk() + end + end + -- Find and evaluate expressions + ktext = string.gsub(ktext, "%%([^%%]*)%%", expression_evaluator) + + return ktext .. text +end + +function do_line(meta, styles, config, line) + if config.workstyle == "" or config.workstyle == line.style then + return default_do_line(meta, styles, config, line) + else + return { n=1, [1]=line } + end +end diff --git a/automation/include/karaskel-adv.lua b/automation/include/karaskel-adv.lua new file mode 100644 index 000000000..eed263f7c --- /dev/null +++ b/automation/include/karaskel-adv.lua @@ -0,0 +1,64 @@ +--[[ + Copyright (c) 2005, Niels Martin Hansen + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Aegisub Group nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + ]] + +-- Aegisub Automation include file +-- This include file is an extension of karaskel.lua providing out-of-the-box support for per-syllable +-- positioning of karaoke. + +-- It automatically includes and re-setups karaskel.lua, so you should not include that yourself! +include("karaskel.lua") +-- Also include utils.lua, since you'll most likely need to use the copy_line function +include("utils.lua") + +-- The interface here has been greatly simplified, there is only one function to override, do_syllable +-- The format for that one has changed. +-- The rest of the functions can be "emulated" through the do_syllable function. +-- Though you can still also override do_line and get an effect our of it, it's not advisable, since that +-- defeats the entire purpose of using this include file. + +-- The arguments here mean the same as in karaskel.lua, and all tables have the same members +-- The return value is different now, though. +-- It is required to be in the same format as the do_line function: +-- A table with an "n" key, and keys 0..n-1 with line structures. +function default_do_syllable(meta, styles, config, line, syl) + return {n=0} +end +do_syllable = default_do_syllable + +function adv_do_line(meta, styles, config, line) + local result = {n=0} + for i = 0, line.karaoke.n-1 do + local out = do_syllable(meta, styles, config, line, line.karaoke[i]) + for j = 1, out.n do + table.insert(result, out[j]) + end + end + return result +end +do_line = adv_do_line diff --git a/automation/include/karaskel.lua b/automation/include/karaskel.lua new file mode 100644 index 000000000..72cbd28a6 --- /dev/null +++ b/automation/include/karaskel.lua @@ -0,0 +1,183 @@ +--[[ + Copyright (c) 2005, Niels Martin Hansen + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Aegisub Group nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + ]] + +-- Aegisub Automation include file +-- The purpose of this include file is to provide a default skeleton for Automation scripts, +-- where you, as a scripter, can just override the functions you need to override. + +-- The following functions can be overridden: +-- do_syllable - called for each syllable in a line +-- do_line_decide - called to decide whether to process a line or not +-- do_line_start - called at the beginning of each line +-- do_line_end - called at the end of each line +-- do_line - Process an entire line +-- By default, these functions are all assigned to the following default functions +-- Your functions should obviously use the same parameter lists etc. +-- Note that if you override do_line, none of the other functions will be called! + + +-- All the functions mentioned here are, however, wrapped in some code that pre-calculates some +-- extra data for the lines and syllables. + +-- The following fields are added to lines: +-- i - The index of the line in the file, 0 being the first +-- width - Rendered width of the line in pixels (using the line style, overrides are not taken into account) +-- height - Rendered height of the line in pixels (note that rotation isn't taken into account either) +-- ascent - Height of the ascenders, in pixels (notice that this looks like the output of text_extents?) +-- extlead - External leading of the font, in pixels (well, it is!) +-- centerleft - Pixel position of the left edge of the line, when horisontally centered on screen +-- centerright - Pixel pisition of the right edge of the line, when horisontally centered on screen +-- duration - Duration of the line in miliseconds +-- styleref - The actual style table for the style this line has + +-- The following fields are added to syllables: +-- i - The index of the syllable in the line, 0 being the first (and usually empty) one +-- width, height, ascent, extlead - Same as for lines +-- left - Left pixel position of the syllable, relative to the start of the line +-- center - Center pixel position of the syllable, also relative to the start of the line +-- right - Right pixel position of the syllable, relative again +-- start_time - Start time of the syllable, in miliseconds, relative to the start of the line +-- end_time - End time of the syllable, similar to start_time + + +-- Return a replacement text for a syllable +function default_do_syllable(meta, styles, config, line, syl) + return syl.text +end + +-- Decide whether or not to process a line +function default_do_line_decide(meta, styles, config, line) + return line.kind == "dialogue" +end + +-- Return a text to prefix the line +function default_do_line_start(meta, styles, config, line) + return "" +end + +-- Return a text to suffix the line +function default_do_line_end(meta, styles, config, line) + return "" +end + +-- Process an entire line (which has pre-calculated extra data in it already) +-- Return a table of replacement lines +function default_do_line(meta, styles, config, line) + -- Check if the line should be processed at all + if not do_line_decide(meta, styles, config, line) then + return {n=0} + end + -- Create a new local var for the line replacement text, set it to line prefix + -- This is to make sure the actual line text isn't replaced before the line has been completely processed + local newtext = do_line_start(meta, styles, config, line) + -- Loop over the syllables + for i = 0, line.karaoke.n-1 do + -- Append the replacement for each syllable onto the line + newtext = newtext .. do_syllable(meta, styles, config, line, line.karaoke[i]) + end + -- Append line suffix + newtext = newtext .. do_line_end(meta, styles, config, line) + -- Now replace the line text + line.text = newtext + -- And return a table with one entry + return {n=1; [1]=line} +end + +-- Now assign all the default functions to the names that are actually called +do_syllable = default_do_syllable +do_line_decide = default_do_line_decide +do_line_start = default_do_line_start +do_line_end = default_do_line_end +do_line = default_do_line + +precalc_start_progress = 0 +precalc_end_progress = 50 +function precalc_syllable_data(meta, styles, lines) + aegisub.set_status("Preparing syllable-data") + for i = 0, lines.n-1 do + aegisub.report_progress(precalc_start_progress + i/lines.n*(precalc_end_progress-precalc_start_progress)) + local line, style = lines[i] + -- Index number of the line + line.i = i + if line.kind == "dialogue" or line.kind == "comment" then + local style = styles[line.style] + -- Line dimensions + line.width, line.height, line.ascent, line.extlead = aegisub.text_extents(style, line.text_stripped) + -- Line position + line.centerleft = math.floor((meta.res_x - line.width) / 2) + line.centerright = meta.res_x - line.centerleft + -- Line duration, in miliseconds + line.duration = (line.end_time - line.start_time) * 10 + -- Style reference + line.styleref = style + -- Process the syllables + local curx, curtime = 0, 0 + for j = 0, line.karaoke.n-1 do + local syl = line.karaoke[j] + -- Syllable index + syl.i = j + -- Syllable dimensions + syl.width, syl.height, syl.ascent, syl.extlead = aegisub.text_extents(style, syl.text_stripped) + -- Syllable positioning + syl.left = curx + syl.center = math.floor(curx + syl.width/2) + syl.right = curx + syl.width + curx = syl.right + -- Start and end times in miliseconds + syl.start_time = curtime + syl.end_time = curtime + syl.duration*10 + curtime = syl.end_time + end + end + end +end + +-- Everything else is done in the process_lines function +function skel_process_lines(meta, styles, lines, config) + -- Do a little pre-calculation for each line and syllable + precalc_syllable_data(meta, styles, lines) + -- A var for the new output + local result = {n=0} + aegisub.set_status("Running main-processing") + -- Now do the usual processing + for i = 0, lines.n-1 do + aegisub.report_progress(50+i/lines.n*50) + if do_line_decide(meta, styles, config, lines[i]) then + -- Get replacement lines + repl = do_line(meta, styles, config, lines[i]) + -- Append to result table + for j = 1, repl.n do + table.insert(result, repl[j]) + end + end + end + -- Done, return the stuff + return result +end +process_lines = skel_process_lines diff --git a/automation/include/readme.txt b/automation/include/readme.txt new file mode 100644 index 000000000..41b2def52 --- /dev/null +++ b/automation/include/readme.txt @@ -0,0 +1,10 @@ +This directory contains standard include files for Automation. +By default, this directory is included in the Automation Include Path, which +means the files here are always available for use with the include() function. + +See the Aegisub help and the individual files in this directory for more +information on their purpose and contents. + +You may add your own files to this directory, they will not be touched upon +uninstalling/upgrading Aegisub. The standard files here will, however, be +removed/replaced on uninstall/upgrade, so you are advised not to edit those! diff --git a/automation/include/utils.lua b/automation/include/utils.lua new file mode 100644 index 000000000..4bb905c6a --- /dev/null +++ b/automation/include/utils.lua @@ -0,0 +1,167 @@ +--[[ + Copyright (c) 2005, Niels Martin Hansen, Rodrigo Braz Monteiro + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the Aegisub Group nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + ]] + +-- Variables with tables only hold references to the actual tables +-- Since a line is a table, a line needs to be copied, otherwise things break in bad ways +function copy_line(input) + output = {} + output.kind = input.kind + if input.kind == "scomment" then + output.text = input.text + elseif input.kind == "comment" or input.kind == "dialogue" then + output.layer = input.layer + output.start_time = input.start_time + output.end_time = input.end_time + output.style = input.style + output.name = input.name + output.margin_l = input.margin_l + output.margin_r = input.margin_r + output.margin_v = input.margin_v + output.effect = input.effect + output.text = input.text + output.text_stripped = input.text_stripped + output.karaoke = input.karaoke -- Don't bother copying the karaoke table, it shouldn't be changed anyway + end + return output +end + + +-- Generates ASS hexadecimal string from R,G,B integer components, in &HBBGGRR& format +function ass_color(r,g,b) + return string.format("&H%02X%02X%02X&",b,g,r) +end + + +-- Converts HSV (Hue, Saturation, Value) to RGB +function HSV_to_RGB(H,S,V) + local r,g,b; + + -- Saturation is zero, make grey + if S == 0 then + r = V*255 + if r < 0 then + r = 0 + end + if r > 255 then + r = 255 + end + g = r + b = r + + -- Else, calculate color + else + -- Calculate subvalues + local Hi = math.floor(H/60) + local f = H/60.0 - Hi + local p = V*(1-S) + local q = V*(1-f*S) + local t = V*(1-(1-f)*S) + + -- Do math based on hue index + if Hi == 0 then + r = V*255.0 + g = t*255.0 + b = p*255.0 + elseif Hi == 1 then + r = q*255.0 + g = V*255.0 + b = p*255.0 + elseif Hi == 2 then + r = p*255.0 + g = V*255.0 + b = t*255.0 + elseif Hi == 3 then + r = p*255.0 + g = q*255.0 + b = V*255.0 + elseif Hi == 4 then + r = t*255.0 + g = p*255.0 + b = V*255.0 + elseif Hi == 5 then + r = V*255.0 + g = p*255.0 + b = q*255.0 + end + end + + r = math.floor(r) + g = math.floor(g) + b = math.floor(b) + return r,g,b +end + + +-- Removes spaces at the start and end of string +function trim (s) + return (string.gsub(s, "^%s*(.-)%s*$", "%1")) +end + + +-- UTF-8 string handling functions +-- Contributed by roxfan + +-- Get the offset for the next character in the string, given the current offset +function next_utf_char(str, off) + local leadb = string.byte(str, off) + if leadb < 128 then + return off+1 + elseif leadb < 224 then + return off+2 + elseif leadb < 240 then + return off+3 + elseif leadb < 248 then + return off+4 + end + aegisub.output_debug(string.format("bad utf-8 in %q at %d",str,off)) + return -1 +end + +-- Get the number of characters in the UTF-8 string (not the number of bytes) +function utf_len(str) + local i = 1 + local len = 0 + while i<=string.len(str) do + i = next_utf_char(str, i) + len = len + 1 + end + -- aegisub.output_debug(string.format("utf_len(%q)=%d",str,len)) + return len +end + + +-- Get the "head" and "tail" of a string, treating it as a sequence of words separated by one or more space-characters +function string.headtail(s) + local a, b, head, tail = string.find(s, "(.-)%s+(.*)") + if a then + return head, tail + else + return s, "" + end +end diff --git a/code-docs/export-system-new-idea.vsd b/code-docs/export-system-new-idea.vsd new file mode 100644 index 0000000000000000000000000000000000000000..401c0d6e65a07d0750cbc20f9e2a523d799de05a GIT binary patch literal 50176 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 literal 0 HcmV?d00001 diff --git a/code-docs/export-system-new.vsd b/code-docs/export-system-new.vsd new file mode 100644 index 0000000000000000000000000000000000000000..98b1d8fbd1b7b40d57a34272fe8fbbe2a59502ec GIT binary patch literal 89088 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

-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

8sdSa${`* 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

mqLJVtfGKV+hm=&LqKMShu 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 literal 0 HcmV?d00001 diff --git a/core/about.cpp b/core/about.cpp new file mode 100644 index 000000000..47a7bfae4 --- /dev/null +++ b/core/about.cpp @@ -0,0 +1,92 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +//////////// +// Includes +#include "about.h" +#include "version.h" +#include "options.h" +#include + + +/////////////// +// Constructor +AboutScreen::AboutScreen(wxWindow *parent,bool easter) +: wxDialog (parent, -1, _("About Aegisub"), wxDefaultPosition, wxSize(300,240), wxSTAY_ON_TOP | wxCAPTION | wxCLOSE_BOX , _("About Aegisub")) +{ + // Get splash + wxBitmap splash; + int splash_n = Options.AsInt(_T("Splash number")); + if (splash_n < 1 || splash_n > 5) splash_n = (rand()%5)+1; + if (splash_n == 1) splash = wxBITMAP(splash_01); + if (splash_n == 2) splash = wxBITMAP(splash_02); + if (splash_n == 3) splash = wxBITMAP(splash_03); + if (splash_n == 4) splash = wxBITMAP(splash_04); + if (splash_n == 5) splash = wxBITMAP(splash_05); + + // Picture + wxSizer *PicSizer = new wxBoxSizer(wxHORIZONTAL); + PicSizer->Add(new BitmapControl(this,splash)); + + // Text sizer + wxSizer *TextSizer = new wxBoxSizer(wxVERTICAL); + TextSizer->Add(new wxStaticText(this,-1,wxString(_T("Aegisub ")) + VERSION_STRING + _(" by ArchMage ZeratuL.\n\nCopyright (c) 2005 - Rodrigo Braz Monteiro. All rights reserved.\nAutomation module and is Copyright (c) 2005 Niels Martin Hansen (aka jfs).\nAll rights reserved.\nCoding by ArchMageZeratuL, jfs, Myrsloik and nmap.\nManual by ArchMage ZeratuL, jfs, movax, Kobi, TheFluff and Jcubed.\nForum hosting by Malakith.\nSee the help file for full credits.")),1); + + // Button sizer + wxSizer *ButtonSizer = new wxBoxSizer(wxHORIZONTAL); + ButtonSizer->AddStretchSpacer(1); + ButtonSizer->Add(new wxButton(this,wxID_OK),0,0,0); + + // Main sizer + wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL); + MainSizer->Add(PicSizer,0,wxBOTTOM,5); + MainSizer->Add(TextSizer,0,wxEXPAND | wxBOTTOM | wxRIGHT | wxLEFT,5); + MainSizer->Add(new wxStaticLine(this,wxID_ANY),0,wxEXPAND | wxALL,5); + MainSizer->Add(ButtonSizer,0,wxEXPAND | wxBOTTOM | wxRIGHT | wxLEFT,5); + + // Set sizer + MainSizer->SetSizeHints(this); + SetSizer(MainSizer); + + // Draw logo + Centre(); +} + + +////////////// +// Destructor +AboutScreen::~AboutScreen () { +} diff --git a/core/about.h b/core/about.h new file mode 100644 index 000000000..9d0749ff1 --- /dev/null +++ b/core/about.h @@ -0,0 +1,52 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +#pragma once + + +//////////// +// Includes +#include +#include "static_bmp.h" + + +/////////////////////// +// Splash screen class +class AboutScreen: public wxDialog { +public: + AboutScreen(wxWindow *parent,bool easter=false); + ~AboutScreen(); +}; diff --git a/core/aegisublocale.cpp b/core/aegisublocale.cpp new file mode 100644 index 000000000..728fca8cc --- /dev/null +++ b/core/aegisublocale.cpp @@ -0,0 +1,128 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +/////////// +// Headers +#include +#include +#include +#include +#include +#include +#include "aegisublocale.h" +#include "main.h" + + +/////////////// +// Constructor +AegisubLocale::AegisubLocale () { + locale = NULL; + curCode = -1; +} + + +////////////// +// Initialize +void AegisubLocale::Init(int language) { + if (language == -1) language = wxLANGUAGE_ENGLISH; + if (locale) delete locale; + curCode = language; + locale = new wxLocale(language); + locale->AddCatalogLookupPathPrefix(AegisubApp::folderName + _T("locale/")); + locale->AddCatalog(_T("aegisub")); + locale->AddCatalog(_T("wxstd")); + setlocale(LC_NUMERIC, "English"); +} + + +/////////////////// +// Pick a language +int AegisubLocale::PickLanguage() { + // Get list + wxArrayInt langs = GetAvailableLanguages(); + + // Check if english is in it, else add it + if (langs.Index(wxLANGUAGE_ENGLISH) == wxNOT_FOUND) { + langs.Insert(wxLANGUAGE_ENGLISH,0); + } + + // Check if user local language is available, if so, make it first + int user = wxLocale::GetSystemLanguage(); + if (langs.Index(user) != wxNOT_FOUND) { + langs.Remove(user); + langs.Insert(user,0); + } + + // Generate names + wxArrayString langNames; + for (size_t i=0;iLanguage); + } + } + } + return final; +} diff --git a/core/aegisublocale.h b/core/aegisublocale.h new file mode 100644 index 000000000..d84373e0b --- /dev/null +++ b/core/aegisublocale.h @@ -0,0 +1,61 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + +#ifndef LOCALE_H +#define LOCALE_H + + +////////////// +// Prototypes +class wxLocale; + + +//////////////////////// +// Aegisub locale class +class AegisubLocale { +private: + wxLocale *locale; + wxArrayInt GetAvailableLanguages(); + +public: + int curCode; + + AegisubLocale(); + void Init(int language); + int PickLanguage(); +}; + + +#endif diff --git a/core/aspell_wrap.cpp b/core/aspell_wrap.cpp new file mode 100644 index 000000000..f9d28f22c --- /dev/null +++ b/core/aspell_wrap.cpp @@ -0,0 +1,94 @@ +// Copyright (c) 2005, Ghassan Nassar +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + +#ifndef NO_SPELLCHECKER + +// +//Includes +// +#include ".\aspell_wrap.h" + + +// +// Aspell Constructor +// +AspellWrapper::AspellWrapper(void) +{ + loaded = false; +} + +// +// AviSynth destructor +// +AspellWrapper::~AspellWrapper(void) +{ + Unload(); +} + +// +// Load Aspell-15.dll +// +void AspellWrapper::Load() { + if (!loaded) { + hLib=LoadLibrary(L"aspell-15.dll"); + if (hLib == NULL) { + throw L"Could not load aspell.dll"; + } + loaded = true; + } +} + + + + +// +// Unloads Aspell-15.dll +// +void AspellWrapper::Unload() { + if (loaded) { + //delete_aspell_config(); + FreeLibrary(hLib); + loaded = false; + } +} + + + +// +// Declare global +// +AspellWrapper Aspell; + +#endif \ No newline at end of file diff --git a/core/aspell_wrap.h b/core/aspell_wrap.h new file mode 100644 index 000000000..9c1e000d3 --- /dev/null +++ b/core/aspell_wrap.h @@ -0,0 +1,68 @@ +// Copyright (c) 2005, Ghassan Nassar +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + +#ifndef NO_SPELLCHECKER + +#ifndef ASPELL_WRAP_H_RKEY9023809128309182 +#define ASPELL_WRAP_H_RKEY9023809128309182 + +////////// +// Headers +extern "C" { +#include +} +#include +#include + +class AspellWrapper +{ +public: + AspellWrapper(void); + ~AspellWrapper(void); + void Load(); + void Unload(); +private: + void Initiate(); + bool loaded; + HINSTANCE hLib; + //wxMutex AviSynthMutex; //ehhhh? Sempahores? + +}; + +extern AspellWrapper Aspell; + +#endif + +#endif diff --git a/core/ass_dialogue.cpp b/core/ass_dialogue.cpp new file mode 100644 index 000000000..72a11fdcd --- /dev/null +++ b/core/ass_dialogue.cpp @@ -0,0 +1,819 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +//////////// +// Includes +#include "ass_dialogue.h" +#include "ass_override.h" +#include "vfr.h" +#include "utils.h" +#include +#include + + +////////////////////// AssEntry ////////////////////// +/////////////////////// +// Constructs AssEntry +AssEntry::AssEntry() { + Type = ENTRY_BASE; + Valid = true; +} + +AssEntry::AssEntry(wxString _data) { + data = _data; + Type = ENTRY_BASE; + Valid = true; +} + + +/////////////////////////// +// Destructor for AssEntry +AssEntry::~AssEntry() { +} + + +/////////////////////////// +// Comparison for STL Sort +bool operator < (const AssEntry &t1, const AssEntry &t2) { + return (t1.StartMS < t2.StartMS); +} + + +////////////////////// AssDialogue ////////////////////// +// Constructs AssDialogue +AssDialogue::AssDialogue() { + Type = ENTRY_DIALOGUE; + group = _T("[Events]"); + + Valid = true; + Start.SetMS(0); + End.SetMS(5000); + StartMS = 0; + Layer = 0; + MarginR = MarginL = MarginV = 0; + Text = _T(""); + Style = _T("Default"); + Actor = _T(""); + Effect = _T(""); + Comment = false; + + UpdateData(); +} + + +AssDialogue::AssDialogue(wxString _data,bool IsSSA) { + Type = ENTRY_DIALOGUE; + group = _T("[Events]"); + data = _data; + Valid = Parse(IsSSA); + if (!Valid) { + throw _T("Failed parsing line."); + } + UpdateData(); +} + + +////////////// +// Destructor +AssDialogue::~AssDialogue () { + Clear(); +} + + +///////// +// Clear +void AssDialogue::Clear () { + using std::vector; + for (vector::iterator cur=Blocks.begin();cur!=Blocks.end();cur++) { + delete *cur; + } + Blocks.clear(); +} + + +////////////////// +// Parse ASS Data +bool AssDialogue::Parse(bool IsSSA) { + size_t pos = 0; + wxString temp; + + // Get type + if (data.substr(pos,9) == _T("Dialogue:")) { + Comment = false; + pos = 10; + } + else if (data.substr(pos,8) == _T("Comment:")) { + Comment = true; + pos = 9; + } + else return false; + wxStringTokenizer tkn(data.Mid(pos),_T(","),wxTOKEN_RET_EMPTY_ALL); + + // Get layer number + if (!tkn.HasMoreTokens()) return false; + temp = tkn.GetNextToken().Trim(false).Trim(true); + if (IsSSA) Layer = 0; + else { + long templ; + temp.ToLong(&templ); + Layer = templ; + } + + // Get start time + if (!tkn.HasMoreTokens()) return false; + Start.ParseASS(tkn.GetNextToken()); + StartMS = Start.GetMS(); + + // Get end time + if (!tkn.HasMoreTokens()) return false; + End.ParseASS(tkn.GetNextToken()); + + // Get style + if (!tkn.HasMoreTokens()) return false; + Style = tkn.GetNextToken(); + Style.Trim(true); + Style.Trim(false); + + // Get actor + if (!tkn.HasMoreTokens()) return false; + Actor = tkn.GetNextToken(); + Actor.Trim(true); + Actor.Trim(false); + + // Get left margin + if (!tkn.HasMoreTokens()) return false; + SetMarginString(tkn.GetNextToken().Trim(false).Trim(true),1); + + // Get right margin + if (!tkn.HasMoreTokens()) return false; + SetMarginString(tkn.GetNextToken().Trim(false).Trim(true),2); + + // Get vertical margin + if (!tkn.HasMoreTokens()) return false; + SetMarginString(tkn.GetNextToken().Trim(false).Trim(true),3); + + // Get effect + if (!tkn.HasMoreTokens()) return false; + Effect = tkn.GetNextToken(); + Effect.Trim(true); + Effect.Trim(false); + + // Get text + Text = tkn.GetNextToken(); + while (tkn.HasMoreTokens()) { + Text += _T(","); + Text += tkn.GetNextToken(); + } + return true; +} + + +////////////////////////////////// +// Update AssDialogue's data line +void AssDialogue::UpdateData () { + // Prepare + data = _T(""); + + // Write all data + if (Comment) data += _T("Comment: "); + else data += _T("Dialogue: "); + + data += wxString::Format(_T("%01i"),Layer); + data += _T(","); + + data += Start.GetASSFormated() + _T(","); + data += End.GetASSFormated() + _T(","); + + Style.Replace(_T(","),_T(";")); + Actor.Replace(_T(","),_T(";")); + data += Style + _T(","); + data += Actor + _T(","); + + data += GetMarginString(1); + data += _T(","); + data += GetMarginString(2); + data += _T(","); + data += GetMarginString(3); + data += _T(","); + + Effect.Replace(_T(","),_T(";")); + data += Effect + _T(","); + data += Text; +} + + +/////////////////////////////// +// Get SSA version of Dialogue +wxString AssDialogue::GetSSAText () { + // Prepare + wxString work = _T(""); + + // Write all work + if (Comment) work += _T("Comment: "); + else work += _T("Dialogue: "); + + work += _T("Marked=0,"); + + work += Start.GetASSFormated() + _T(","); + work += End.GetASSFormated() + _T(","); + + Style.Replace(_T(","),_T(";")); + Actor.Replace(_T(","),_T(";")); + work += Style + _T(","); + work += Actor + _T(","); + + work += GetMarginString(1); + work += _T(","); + work += GetMarginString(2); + work += _T(","); + work += GetMarginString(3); + work += _T(","); + + Effect.Replace(_T(","),_T(";")); + work += Effect + _T(","); + work += Text; + + return work; +} + + +////////////////// +// Parse SRT tags +// -------------- +// Yea, I convert to ASS tags, then parse that. So sue me. +void AssDialogue::ParseSRTTags () { + // Search and replace + size_t total = 0; + total += Text.Replace(_T(""),_T("{\\i1}")); + total += Text.Replace(_T(""),_T("{\\i0}")); + total += Text.Replace(_T(""),_T("{\\b1}")); + total += Text.Replace(_T(""),_T("{\\b0}")); + total += Text.Replace(_T(""),_T("{\\u1}")); + total += Text.Replace(_T(""),_T("{\\u0}")); + total += Text.Replace(_T(""),_T("{\\s1}")); + total += Text.Replace(_T(""),_T("{\\s0}")); + + // Process tag + wxString work = Text; + work.UpperCase(); + size_t pos_open = 0; + size_t pos_close = 0; + size_t pos = 0; + size_t end = 0; + size_t start = 0; + bool isOpen; + + // Iterate + pos_open = work.find(_T(""),start)+1; + //if (end == wxString::npos) continue; + + // Open tag + if (isOpen) { + wxString replaced = _T(""); + + // Color tag + if ((pos = work.find(_T("COLOR=\""),start)) != wxString::npos) { + if (pos < end) { + pos += 7; + size_t end_tag = Text.find(_T("\""),pos); + if (end_tag != wxString::npos) { + if (end_tag-pos == 7) { + replaced += _T("{\\c&H"); + replaced += work.substr(pos+5,2); + replaced += work.substr(pos+3,2); + replaced += work.substr(pos+1,2); + replaced += _T("&}"); + total++; + } + } + } + } + + // Face tag + if ((pos = work.find(_T("FACE=\""),start)) != wxString::npos) { + if (pos < end) { + pos += 6; + size_t end_tag = work.find(_T("\""),pos); + if (end_tag != wxString::npos) { + replaced += _T("{\\fn"); + replaced += work.substr(pos,end_tag-pos); + replaced += _T("}"); + total++; + } + } + } + + // Size tag + if ((pos = work.find(_T("SIZE=\""),start)) != wxString::npos) { + if (pos < end) { + pos += 6; + size_t end_tag = Text.find(_T("\""),pos); + if (end_tag != wxString::npos) { + replaced += _T("{\\fs"); + replaced += work.substr(pos,end_tag-pos); + replaced += _T("}"); + total++; + } + } + } + + // Replace whole tag + Text = Text.substr(0,start) + replaced + Text.substr(end); + total++; + } + + // Close tag + else { + // Find if it's italic, bold, underline, and strikeout + wxString prev = Text.Left(start); + bool isItalic=false,isBold=false,isUnder=false,isStrike=false; + if (CountMatches(prev,_T("{\\i1}")) > CountMatches(prev,_T("{\\i0}"))) isItalic = true; + if (CountMatches(prev,_T("{\\b1}")) > CountMatches(prev,_T("{\\b0}"))) isBold = true; + if (CountMatches(prev,_T("{\\u1}")) > CountMatches(prev,_T("{\\u0}"))) isUnder = true; + if (CountMatches(prev,_T("{\\s1}")) > CountMatches(prev,_T("{\\s0}"))) isStrike = true; + + // Generate new tag, by reseting and then restoring flags + wxString replaced = _T("{\\r"); + if (isItalic) replaced += _T("\\i1"); + if (isBold) replaced += _T("\\b1"); + if (isUnder) replaced += _T("\\u1"); + if (isStrike) replaced += _T("\\s1"); + replaced += _T("}"); + + // Replace + Text = Text.substr(0,start) + replaced + Text.substr(end); + total++; + } + + // Get next + work = Text; + work.UpperCase(); + pos_open = work.find(_T(" 0) UpdateText(); + UpdateData(); +} + + +////////////////// +// Parse ASS tags +void AssDialogue::ParseASSTags () { + // Clear blocks + for (size_t i=0;iparent = this; + block->text = work; + block->ParseTags(); + Blocks.push_back(block); + + // Look for \p in block + std::vector::iterator curTag; + for (curTag = block->Tags.begin();curTag != block->Tags.end();curTag++) { + AssOverrideTag *tag = *curTag; + if (tag->Name == _T("\\p")) { + drawingLevel = tag->Params.at(0)->AsInt(); + } + } + + // Increase + cur = end+1; + } + + // Plain-text/drawing block + else { + wxString work; + end = Text.find(_T("{"),cur); + if (end == wxString::npos) { + work = Text.substr(cur); + end = len; + } + else work = Text.substr(cur,end-cur); + + // Plain-text + if (drawingLevel == 0) { + AssDialogueBlockPlain *block = new AssDialogueBlockPlain; + block->text = work; + Blocks.push_back(block); + } + + // Drawing + else { + AssDialogueBlockDrawing *block = new AssDialogueBlockDrawing; + block->text = work; + block->Scale = drawingLevel; + Blocks.push_back(block); + } + + cur = end; + } + } + + // Empty line, make an empty block + if (len == 0) { + AssDialogueBlockPlain *block = new AssDialogueBlockPlain; + block->text = _T(""); + Blocks.push_back(block); + } +} + + +////////////// +// Strip tags +void AssDialogue::StripTags () { + using std::list; + using std::vector; + vector::iterator next; + for (vector::iterator cur=Blocks.begin();cur!=Blocks.end();cur=next) { + next = cur; + next++; + if ((*cur)->type == BLOCK_OVERRIDE) { + delete *cur; + Blocks.erase(cur); + } + } + UpdateText(); + UpdateData(); +} + + +/////////////////////// +// Convert tags to SRT +// ------------------- +// TODO: Improve this code +// +void AssDialogue::ConvertTagsToSRT () { + // Setup + using std::list; + using std::vector; + AssDialogueBlockOverride* curBlock; + AssDialogueBlockPlain *curPlain; + AssOverrideTag* curTag; + wxString final = _T(""); + bool isItalic=false,isBold=false,isUnder=false,isStrike=false; + bool temp; + + // Iterate through blocks + for (size_t i=0;iTags.size();j++) { + curTag = curBlock->Tags.at(j); + if (curTag->IsValid()) { + // Italics + if (curTag->Name == _T("\\i")) { + temp = curTag->Params.at(0)->AsBool(); + if (temp && !isItalic) { + isItalic = true; + final += _T(""); + } + if (!temp && isItalic) { + isItalic = false; + final += _T(""); + } + } + + // Underline + if (curTag->Name == _T("\\u")) { + temp = curTag->Params.at(0)->AsBool(); + if (temp && !isUnder) { + isUnder = true; + final += _T(""); + } + if (!temp && isUnder) { + isUnder = false; + final += _T(""); + } + } + + // Strikeout + if (curTag->Name == _T("\\s")) { + temp = curTag->Params.at(0)->AsBool(); + if (temp && !isStrike) { + isStrike = true; + final += _T(""); + } + if (!temp && isStrike) { + isStrike = false; + final += _T(""); + } + } + + // Bold + if (curTag->Name == _T("\\b")) { + temp = curTag->Params.at(0)->AsBool(); + if (temp && !isBold) { + isBold = true; + final += _T(""); + } + if (!temp && isBold) { + isBold = false; + final += _T(""); + } + } + } + } + } + + // Plain text + else { + curPlain = AssDialogueBlock::GetAsPlain(Blocks.at(i)); + if (curPlain) { + final += curPlain->GetText(); + } + } + } + + Text = final; + UpdateData(); +} + + +////////////////////////// +// Updates text from tags +void AssDialogue::UpdateText () { + using std::vector; + Text = _T(""); + for (vector::iterator cur=Blocks.begin();cur!=Blocks.end();cur++) { + if ((*cur)->type == BLOCK_OVERRIDE) { + Text += _T("{"); + Text += (*cur)->GetText(); + Text += _T("}"); + } + else Text += (*cur)->GetText(); + } +} + + +///////////////////////////// +// Sets margin from a string +void AssDialogue::SetMarginString(const wxString origvalue,int which) { + // Make it numeric + wxString strvalue = origvalue; + if (!strvalue.IsNumber()) { + strvalue = _T(""); + for (size_t i=0;i 9999) value = 9999; + + // Assign + switch (which) { + case 1: MarginL = value; break; + case 2: MarginR = value; break; + case 3: MarginV = value; break; + default: throw _T("Invalid margin"); + } +} + + +////////////////////////// +// Gets string for margin +wxString AssDialogue::GetMarginString(int which) { + int value; + switch (which) { + case 1: value = MarginL; break; + case 2: value = MarginR; break; + case 3: value = MarginV; break; + default: throw _T("Invalid margin"); + } + wxString result = wxString::Format(_T("%04i"),value); + return result; +} + + +/////////////////////////////////// +// Process parameters via callback +void AssDialogue::ProcessParameters(void (*callback)(wxString tagName,int par_n,AssOverrideParameter *param,void *userData),void *userData) { + // Apply for all override blocks + AssDialogueBlockOverride *curBlock; + for (std::vector::iterator cur=Blocks.begin();cur!=Blocks.end();cur++) { + if ((*cur)->type == BLOCK_OVERRIDE) { + curBlock = static_cast (*cur); + curBlock->ProcessParameters(callback,userData); + } + } +} + + +/////////////////////////////// +// Checks if two lines collide +bool AssDialogue::CollidesWith(AssDialogue *target) { + if (!target) return false; + int a = Start.GetMS(); + int b = End.GetMS(); + int c = target->Start.GetMS(); + int d = target->End.GetMS(); + return ((a < c) ? (c < b) : (a < d)); +} + + +////////////////////// AssDialogueBlock ////////////////////// +/////////////// +// Constructor +AssDialogueBlock::AssDialogueBlock () { + type = BLOCK_BASE; +} + + +////////////// +// Destructor +AssDialogueBlock::~AssDialogueBlock () { +} + + +//////////////////////////// +// Returns as a plain block +// ---------------------- +// If it isn't a plain block, returns NULL +AssDialogueBlockPlain *AssDialogueBlock::GetAsPlain(AssDialogueBlock *base) { + if (!base) return NULL; + if (base->type == BLOCK_PLAIN) { + return static_cast (base); + } + return NULL; +} + + +//////////////////////////////// +// Returns as an override block +// ---------------------------- +// If it isn't an override block, returns NULL +AssDialogueBlockOverride *AssDialogueBlock::GetAsOverride(AssDialogueBlock *base) { + if (!base) return NULL; + if (base->type == BLOCK_OVERRIDE) { + return static_cast (base); + } + return NULL; +} + + +////////////////////////////// +// Returns as a drawing block +// ---------------------------- +// If it isn't an drawing block, returns NULL +AssDialogueBlockDrawing *AssDialogueBlock::GetAsDrawing(AssDialogueBlock *base) { + if (!base) return NULL; + if (base->type == BLOCK_DRAWING) { + return static_cast (base); + } + return NULL; +} + + +////////////////////// AssDialogueBlockPlain ////////////////////// +/////////////// +// Constructor +AssDialogueBlockPlain::AssDialogueBlockPlain () { + type = BLOCK_PLAIN; +} + + +/////////////////// +// Return the text +wxString AssDialogueBlockPlain::GetText() { + return text; +} + + +////////////////////// AssDialogueBlockDrawing ////////////////////// +/////////////// +// Constructor +AssDialogueBlockDrawing::AssDialogueBlockDrawing () { + type = BLOCK_DRAWING; +} + + +/////////////////// +// Return the text +wxString AssDialogueBlockDrawing::GetText() { + return text; +} + + +//////////////////////// +// Multiply coordinates +void AssDialogueBlockDrawing::MultiplyCoords(double x,double y) { + // HACK: Implement a proper parser ffs!! + wxStringTokenizer tkn(GetText(),_T(" "),wxTOKEN_DEFAULT); + wxString cur; + wxString final; + bool isX = true; + long temp; + + // Process tokens + while (tkn.HasMoreTokens()) { + cur = tkn.GetNextToken().Lower(); + + // Number, process it + if (cur.IsNumber()) { + // Multiply it + cur.ToLong(&temp); + if (isX) temp = temp*x + 0.5; + else temp = temp*y + 0.5; + + // Write back to list + final += wxString::Format(_T("%i "),temp); + + // Toggle X/Y + isX = !isX; + } + + // Text + else { + if (cur == _T("m") || cur == _T("n") || cur == _T("l") || cur == _T("b") || cur == _T("s") || cur == _T("p") || cur == _T("c")) isX = true; + final += cur + _T(" "); + } + } + + // Write back final + final = final.Left(final.Length()-1); + text = final; +} diff --git a/core/ass_dialogue.h b/core/ass_dialogue.h new file mode 100644 index 000000000..637b51b6e --- /dev/null +++ b/core/ass_dialogue.h @@ -0,0 +1,186 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +#pragma once + + +//////////// +// Includes +#include +#include "ass_entry.h" +#include "ass_time.h" + + +////////////// +// Prototypes +class AssOverrideParameter; +class AssOverrideTag; +class AssDialogueBlockPlain; +class AssDialogueBlockOverride; +class AssDialogueBlockDrawing; + + +/////////////////// +// Block type enum +enum ASS_BlockType { + BLOCK_BASE, + BLOCK_PLAIN, + BLOCK_OVERRIDE, + BLOCK_DRAWING +}; + + +////////////////////// +// AssDialogue Blocks +// ------------------ +// A block is each group in the text field of an AssDialogue +// For example: +// Yes, I {\i1}am{\i0} here. +// +// Gets split in five blocks: +// "Yes, I " (Plain) +// "\i1" (Override) +// "am" (Plain) +// "\i0" (Override) +// " here." (Plain) +// +// Also note how {}s are discarded. +// Override blocks are further divided in AssOverrideTag's. +// +// The GetText() method generates a new value for the "text" field from +// the other fields in the specific class, and returns the new value. +// +// TODO: Support for {\p#}...{\p0} +// +class AssDialogueBlock { +public: + wxString text; + ASS_BlockType type; + AssDialogue *parent; + + AssDialogueBlock(); + virtual ~AssDialogueBlock(); + + virtual wxString GetText() = 0; // make the class abstract + static AssDialogueBlockPlain *GetAsPlain(AssDialogueBlock *base); // Returns a block base as a plain block if it is valid, null otherwise + static AssDialogueBlockOverride *GetAsOverride(AssDialogueBlock *base); // Returns a block base as an override block if it is valid, null otherwise + static AssDialogueBlockDrawing *GetAsDrawing(AssDialogueBlock *base); // Returns a block base as a drawing block if it is valid, null otherwise +}; + + +///////////////////////////// +// Plain dialogue block text +// ------------------------- +// This is used for standard text +// type = BLOCK_PLAIN +// +class AssDialogueBlockPlain : public AssDialogueBlock { +public: + AssDialogueBlockPlain(); + wxString GetText(); +}; + + +///////////////////////////// +// Plain dialogue block text +// ------------------------- +// This is used for drawing commands +// type = BLOCK_DRAWING +// +class AssDialogueBlockDrawing : public AssDialogueBlock { +public: + int Scale; + + AssDialogueBlockDrawing(); + void MultiplyCoords(double x,double y); + wxString GetText(); +}; + + +////////////////////// +// Override tag block +// ------------------ +// Used to store ASS overrides +// Type = BLOCK_OVERRIDE +// +class AssDialogueBlockOverride : public AssDialogueBlock { +public: + AssDialogueBlockOverride(); + ~AssDialogueBlockOverride(); + std::vector Tags; + + wxString GetText(); + void ParseTags(); // Parses tags + void ProcessParameters(void (*callback)(wxString,int,AssOverrideParameter*,void *),void *userData); +}; + + +//////////////////////////////////////// +// Class for Dialogue and Comment lines +class AssDialogue : public AssEntry { +public: + std::vector Blocks; // Contains information about each block of text + + bool Comment; // Is this a comment line? + int Layer; // Layer number + int MarginR; // Right margin + int MarginL; // Left margin + int MarginV; // Vertical margin + AssTime Start; // Starting time + AssTime End; // Ending time + wxString Style; // Style name + wxString Actor; // Actor name + wxString Effect; // Effect name + wxString Text; // Raw text data + + bool Parse(bool IsSSA=false); // Parses raw ASS data into everything else + void ParseASSTags(); // Parses text to generate block information (doesn't update data) + void ParseSRTTags(); // Converts tags to ass format and calls ParseASSTags+UpdateData + void UpdateData(); // Updates raw data from current values + text + void UpdateText(); // Generates text from the override tags + void ConvertTagsToSRT(); // Converts tags to SRT format + void StripTags(); // Strips all tags from the text + void Clear(); // Wipes all data + void SetMarginString(const wxString value,int which); // Set string to a margin value (1 = left, 2 = right, 3 = vertical) + wxString GetMarginString(int which); // Returns the string of a margin value (1 = left, 2 = right, 3 = vertical) + void ProcessParameters(void (*callback)(wxString,int,AssOverrideParameter*,void *userData),void *userData=NULL); // Callback to process parameters + wxString GetSSAText(); + bool CollidesWith(AssDialogue *target); // Checks if two lines collide + + AssDialogue(); + AssDialogue(wxString data,bool IsSSA=false); + ~AssDialogue(); +}; diff --git a/core/ass_entry.cpp b/core/ass_entry.cpp new file mode 100644 index 000000000..74afa6e8f --- /dev/null +++ b/core/ass_entry.cpp @@ -0,0 +1,76 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +/////////// +// Headers +#include "ass_dialogue.h" +#include "ass_style.h" +#include "ass_entry.h" + + +//////////////////////////////////////////////////////////////// +// Returns an entry as dialogue if possible, else, returns NULL +AssDialogue *AssEntry::GetAsDialogue(AssEntry *base) { + if (!base) return NULL; + if (base->Type == ENTRY_DIALOGUE) { + return static_cast (base); + } + return NULL; +} + + +///////////////////////////////////////////////////////////// +// Returns an entry as style if possible, else, returns NULL +AssStyle *AssEntry::GetAsStyle(AssEntry *base) { + if (!base) return NULL; + if (base->Type == ENTRY_STYLE) { + return static_cast (base); + } + return NULL; +} + + +////////////////////// +// Get SSA conversion +wxString AssEntry::GetSSAText() { + if (data.Lower() == _T("[v4+ styles]")) return wxString(_T("[V4 Styles]")); + if (data.Lower() == _T("scripttype: v4.00+")) return wxString(_T("ScriptType: v4.00")); + if (data.Lower().Left(7) == _T("format:")) { + if (group.Lower() == _T("[events]")) return wxString(_T("Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text")); + if (group.Lower() == _T("[v4+ styles]")) return wxString(_T("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding")); + } + return data; +} diff --git a/core/ass_entry.h b/core/ass_entry.h new file mode 100644 index 000000000..346c57670 --- /dev/null +++ b/core/ass_entry.h @@ -0,0 +1,80 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +#pragma once + + +/////////// +// Headers +#include + + +////////////// +// Prototypes +class AssDialogue; +class AssStyle; + + +/////////////////// +// Entry type enum +enum ASS_EntryType { + ENTRY_BASE, + ENTRY_DIALOGUE, + ENTRY_STYLE +}; + + +//////////////////////////////////// +// Base class for each line in file +class AssEntry { +public: + int StartMS; // This is only stored for sorting issues, in order to keep non-dialogue lines aligned + bool Valid; // Flags as valid or not + wxString data; // Raw data, exactly the same line that appears on the .ass (note that this will be in ass even if source wasn't) + wxString group; // Group it belongs to, e.g. "[Events]" + ASS_EntryType Type; // Defines if this is a dialogue, style, or unknown line + + AssEntry(); + AssEntry(wxString data); + virtual ~AssEntry(); + + virtual wxString GetSSAText(); + static AssDialogue *GetAsDialogue(AssEntry *base); // Returns an entry base as a dialogue if it is valid, null otherwise + static AssStyle *GetAsStyle(AssEntry *base); // Returns an entry base as a style if it is valid, null otherwise +}; + +// This operator is for sorting +bool operator < (const AssEntry &t1, const AssEntry &t2); diff --git a/core/ass_export_filter.cpp b/core/ass_export_filter.cpp new file mode 100644 index 000000000..b1ac60bcb --- /dev/null +++ b/core/ass_export_filter.cpp @@ -0,0 +1,177 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +/////////// +// Headers +#include "ass_export_filter.h" +#include "ass_file.h" + + +/////////////// +// Constructor +AssExportFilter::AssExportFilter() { + autoExporter = false; + initialized = false; + FilterList *fil = AssExportFilterChain::GetUnpreparedFilterList(); + fil->push_back(this); +} + + +////////////// +// Destructor +AssExportFilter::~AssExportFilter() { + try { + Unregister(); + } + catch (...) { + // Ignore error + } +} + + +//////////// +// Register +void AssExportFilter::Register (wxString name,int priority) { + // Check if it's registered + if (RegisterName != _T("")) { + throw _T("Register export filter: filter with name \"") + name + _T("\" is already registered."); + } + + // Remove pipes from name + name.Replace(_T("|"),_T("")); + + // Check if name exists + FilterList::iterator begin = AssExportFilterChain::GetFilterList()->begin(); + FilterList::iterator end = AssExportFilterChain::GetFilterList()->end(); + for (FilterList::iterator cur=begin;cur!=end;cur++) { + if ((*cur)->RegisterName == name) { + throw _T("Register export filter: name \"") + name + _T("\" already exists."); + } + } + + // Set name + RegisterName = name; + Priority = priority; + + // Look for place to insert + bool inserted = false; + for (FilterList::iterator cur=begin;cur!=end;cur++) { + if ((*cur)->Priority < Priority) { + AssExportFilterChain::GetFilterList()->insert(cur,this); + inserted = true; + break; + } + } + if (!inserted) AssExportFilterChain::GetFilterList()->push_back(this); +} + + +////////////// +// Unregister +void AssExportFilter::Unregister () { + // Check if it's registered + if (!IsRegistered()) throw _T("Unregister export filter: name \"") + RegisterName + _T("\" is not registered."); + + // Unregister + RegisterName = _T(""); + AssExportFilterChain::GetFilterList()->remove(this); +} + + +///////////////////////////// +// Checks if it's registered +bool AssExportFilter::IsRegistered() { + // Check name + if (RegisterName == _T("")) { + return false; + } + + // Check list + bool found = false; + FilterList::iterator begin = AssExportFilterChain::GetFilterList()->begin(); + FilterList::iterator end = AssExportFilterChain::GetFilterList()->end(); + for (FilterList::iterator cur=begin;cur!=end;cur++) { + if ((*cur) == this) { + found = true; + break; + } + } + return found; +} + + +///////////// +// Get sizer +wxWindow *AssExportFilter::GetConfigDialogWindow(wxWindow *parent) { + return NULL; +} + + +//////////////////// +// Config dialog OK +void AssExportFilter::LoadSettings(bool IsDefault) { +} + + +/////////////// +// Static list +AssExportFilterChain *AssExportFilterChain::instance=NULL; + + +//////////// +// Get list +FilterList *AssExportFilterChain::GetFilterList() { + if (instance == NULL) instance = new AssExportFilterChain(); + return &(instance->Filters); +} + + +/////////////////////// +// Get unprepared list +FilterList *AssExportFilterChain::GetUnpreparedFilterList() { + if (instance == NULL) instance = new AssExportFilterChain(); + return &(instance->Unprepared); +} + + +/////////////////// +// Prepare filters +void AssExportFilterChain::PrepareFilters() { + for (FilterList::iterator cur=instance->Unprepared.begin();cur!=instance->Unprepared.end();cur++) { + (*cur)->Init(); + } + instance->Unprepared.clear(); +} diff --git a/core/ass_export_filter.h b/core/ass_export_filter.h new file mode 100644 index 000000000..dcefe805b --- /dev/null +++ b/core/ass_export_filter.h @@ -0,0 +1,105 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +#pragma once + + +/////////// +// Headers +#include +#include + + +////////////// +// Prototypes +class AssFile; +class AssExportFilter; +class DialogExport; +class AssExporter; + + +//////////// +// Typedefs +typedef std::list FilterList; + + +////////////////////////////////////// +// Singleton for storing filter chain +class AssExportFilterChain { + friend class AssExportFilter; + friend class AssExporter; + +private: + FilterList Filters; + FilterList Unprepared; + + static AssExportFilterChain *instance; + static FilterList *GetFilterList(); + static FilterList *GetUnpreparedFilterList(); + +public: + static void PrepareFilters(); +}; + + +//////////////////////////// +// Base export filter class +class AssExportFilter { + friend class AssExporter; + friend class AssExportFilterChain; + +private: + wxString RegisterName; + int Priority; + +protected: + bool autoExporter; + bool initialized; + wxString Description; + + void Register(wxString name,int priority=0); // Register the filter with specific name. Higher priority filters get the file to process first. + void Unregister(); // Unregister the filter instance + bool IsRegistered(); // Is this instance registered as a filter? + virtual void Init()=0; // Tell it to initialize itself + +public: + AssExportFilter(); + virtual ~AssExportFilter(); + + virtual void ProcessSubs(AssFile *subs)=0; // Process subtitles - this function must be overriden. + virtual wxWindow *GetConfigDialogWindow(wxWindow *parent); // Draw setup controls - this function may optionally be overridden. + virtual void LoadSettings(bool IsDefault); // Config dialog is done - extract data now. +}; diff --git a/core/ass_exporter.cpp b/core/ass_exporter.cpp new file mode 100644 index 000000000..8222082c4 --- /dev/null +++ b/core/ass_exporter.cpp @@ -0,0 +1,180 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +/////////// +// Headers +#include "ass_exporter.h" +#include "ass_export_filter.h" +#include "ass_file.h" +#include "frame_main.h" + + +/////////////// +// Constructor +AssExporter::AssExporter (AssFile *subs) { + OriginalSubs = subs; + IsDefault = true; +} + + +////////////// +// Destructor +AssExporter::~AssExporter () { +} + + +///////////////////////// +// Draw control settings +void AssExporter::DrawSettings(wxWindow *parent,wxSizer *AddTo) { + IsDefault = false; + wxWindow *window; + wxSizer *box; + FilterList::iterator begin = AssExportFilterChain::GetFilterList()->begin(); + FilterList::iterator end = AssExportFilterChain::GetFilterList()->end(); + for (FilterList::iterator cur=begin;cur!=end;cur++) { + window = (*cur)->GetConfigDialogWindow(parent); + if (window) { + box = new wxStaticBoxSizer(wxVERTICAL,parent,(*cur)->RegisterName); + box->Add(window,0,wxEXPAND,0); + AddTo->Add(box,0,wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM,5); + AddTo->Show(box,false); + Sizers[(*cur)->RegisterName] = box; + } + } +} + + +/////////////////////// +// Add filter to chain +void AssExporter::AddFilter(wxString name) { + // Get filter + AssExportFilter *filter = NULL; + FilterList::iterator begin = AssExportFilterChain::GetFilterList()->begin(); + FilterList::iterator end = AssExportFilterChain::GetFilterList()->end(); + for (FilterList::iterator cur=begin;cur!=end;cur++) { + if ((*cur)->RegisterName == name) { + filter = *cur; + } + } + + // Check + if (!filter) throw _T("Filter not found"); + + // Add to list + Filters.push_back(filter); +} + + +/////////////////////////////////////////// +// Adds all autoexporting filters to chain +void AssExporter::AddAutoFilters() { + FilterList::iterator begin = AssExportFilterChain::GetFilterList()->begin(); + FilterList::iterator end = AssExportFilterChain::GetFilterList()->end(); + for (FilterList::iterator cur=begin;cur!=end;cur++) { + if ((*cur)->autoExporter) { + Filters.push_back(*cur); + } + } +} + + +/////////////////////////// +// Get name of all filters +wxArrayString AssExporter::GetAllFilterNames() { + wxArrayString names; + FilterList::iterator begin = AssExportFilterChain::GetFilterList()->begin(); + FilterList::iterator end = AssExportFilterChain::GetFilterList()->end(); + for (FilterList::iterator cur=begin;cur!=end;cur++) { + names.Add((*cur)->RegisterName); + } + return names; +} + + +////////// +// Export +void AssExporter::Export(wxString filename, wxString charset) { + // Copy + AssFile *Subs = new AssFile(*OriginalSubs); + + // Run filters + for (FilterList::iterator cur=Filters.begin();cur!=Filters.end();cur++) { + (*cur)->LoadSettings(IsDefault); + (*cur)->ProcessSubs(Subs); + } + + /* + // Set charset + bool withCharset = !IsDefault; + wxString charset = _T(""); + if (withCharset) { + wxArrayString choices = FrameMain::GetEncodings(); + charset = wxGetSingleChoice(_T("Choose charset code:"), _T("Charset"),choices,NULL,-1, -1,true,250,200); + if (charset == _T("")) { + delete Subs; + return; + } + } + */ + // *FIXME* (or is it?) We assume charset argument is valid here + + // Save + Subs->Save(filename,false,false,charset); + delete Subs; +} + + +/////////////////////////////////// +// Get window associated with name +wxSizer *AssExporter::GetSettingsSizer(wxString name) { + SizerMap::iterator pos = Sizers.find(name); + if (pos == Sizers.end()) return NULL; + else return pos->second; +} + + +///////////////////////////// +// Get description of filter +wxString AssExporter::GetDescription(wxString name) { + FilterList::iterator begin = AssExportFilterChain::GetFilterList()->begin(); + FilterList::iterator end = AssExportFilterChain::GetFilterList()->end(); + for (FilterList::iterator cur=begin;cur!=end;cur++) { + if ((*cur)->RegisterName == name) { + return (*cur)->Description; + } + } + throw _T("Filter not found."); +} diff --git a/core/ass_exporter.h b/core/ass_exporter.h new file mode 100644 index 000000000..3c0bda70e --- /dev/null +++ b/core/ass_exporter.h @@ -0,0 +1,80 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +#pragma once + + +/////////// +// Headers +#include +#include +#include + + +////////////// +// Prototypes +class AssExportFilter; +class AssFile; + + +//////////// +// Typedefs +typedef std::list FilterList; +typedef std::map SizerMap; + + +////////////////// +// Exporter class +class AssExporter { +private: + SizerMap Sizers; + FilterList Filters; + AssFile *OriginalSubs; + bool IsDefault; + +public: + AssExporter(AssFile *subs); + ~AssExporter(); + + wxArrayString GetAllFilterNames(); + void AddFilter(wxString name); + void AddAutoFilters(); + void DrawSettings(wxWindow *parent,wxSizer *AddTo); + wxSizer *GetSettingsSizer(wxString name); + void Export(wxString file, wxString charset = _T("")); + AssFile *GetOriginalSubs() { return OriginalSubs; } + wxString GetDescription(wxString name); +}; diff --git a/core/ass_file.cpp b/core/ass_file.cpp new file mode 100644 index 000000000..661ae5422 --- /dev/null +++ b/core/ass_file.cpp @@ -0,0 +1,1021 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +//////////// +// Includes +#include "ass_file.h" +#include "ass_dialogue.h" +#include "ass_style.h" +#include "ass_override.h" +#include "ass_exporter.h" +#include "vfr.h" +#include "options.h" +#include "text_file_reader.h" +#include "text_file_writer.h" +#include "version.h" +#include + + +////////////////////// AssFile ////////////////////// +/////////////////////// +// AssFile constructor +AssFile::AssFile () { + AssOverrideTagProto::LoadProtos(); + Clear(); +} + + +////////////////////// +// AssFile destructor +AssFile::~AssFile() { + Clear(); +} + + +///////////////////// +// Load generic subs +void AssFile::Load (const wxString _filename,const wxString charset) { + bool ok = true; + + try { + // Try to open file + std::ifstream file; + file.open(_filename.mb_str(wxConvLocal)); + if (!file.is_open()) { + throw _T("Unable to open file \"") + _filename + _T("\". Check if it exists and if you have permissions to read it."); + } + file.close(); + + // Find file encoding + wxString enc; + if (charset == _T("")) enc = TextFileReader::GetEncoding(_filename); + else enc = charset; + TextFileReader::EnsureValid(enc); + + // Get extension + int i = 0; + for (i=(int)_filename.size();--i>=0;) { + if (_filename[i] == _T('.')) break; + } + wxString extension = _filename.substr(i+1); + extension.Lower(); + + // Generic preparation + Clear(); + IsASS = false; + + // ASS + if (extension == _T("ass")) { + LoadASS(_filename,enc,false); + } + + // SRT + else if (extension == _T("srt")) { + LoadSRT(_filename,enc); + } + + // SSA + else if (extension == _T("ssa")) { + LoadASS(_filename,enc,true); + } + + // TXT + else if (extension == _T("txt")) { + LoadTXT(_filename,enc); + } + + // Couldn't find a type + else { + wxString error = _T("Unknown file type: "); + error += extension; + throw error; + } + } + + // String error + catch (wchar_t *except) { + wxMessageBox(except,_T("Error loading file"),wxICON_ERROR | wxOK); + ok = false; + } + + catch (wxString except) { + wxMessageBox(except,_T("Error loading file"),wxICON_ERROR | wxOK); + ok = false; + } + + // Other error + catch (...) { + wxMessageBox(_T("Unknown error"),_T("Error loading file"),wxICON_ERROR | wxOK); + ok = false; + } + + // Verify loading + if (ok) filename = _filename; + else LoadDefault(); + + // Set general data + loaded = true; + + // Add comments and set vars + AddComment(_T("Script generated by Aegisub ") + wxString(VERSION_STRING)); + AddComment(_T("http://www.aegisub.net")); + SetScriptInfo(_T("ScriptType"),_T("v4.00+")); + AddToRecent(_filename); +} + + +/////////////////////////// +// Load Ass file from disk +void AssFile::LoadASS (const wxString _filename,const wxString encoding,bool IsSSA) { + using namespace std; + + // Reader + TextFileReader file(_filename,encoding); + + // Parse file + wxString curgroup; + int lasttime = -1; + while (file.HasMoreLines()) { + // Reads line + wxString wxbuffer = file.ReadLineFromFile(); + + // Convert v4 styles to v4+ styles + if (wxbuffer.Lower() == _T("[v4 styles]")) { + wxbuffer = _T("[V4+ Styles]"); + } + + // Set group + if (wxbuffer[0] == _T('[')) { + curgroup = wxbuffer; + } + + // Add line + try { + lasttime = AddLine(wxbuffer,curgroup,lasttime,IsSSA); + } + catch (wchar_t *err) { + Clear(); + throw wxString(_T("Error processing line: ")) + wxbuffer + _T(": ") + wxString(err); + } + catch (...) { + Clear(); + throw wxString(_T("Error processing line: ")) + wxbuffer; + } + } + + // Set ASS + IsASS = !IsSSA; +} + + +//////////////////////////// +// Loads SRT subs from disk +void AssFile::LoadSRT (wxString _filename,wxString encoding) { + using namespace std; + + // Reader + TextFileReader file(_filename,encoding); + + // Default + LoadDefault(false); + IsASS = false; + + // Parse file + int linen = 1; + int fileLine = 0; + int mode = 0; + long templ; + AssDialogue *line = NULL; + while (file.HasMoreLines()) { + // Reads line + wxString curline = file.ReadLineFromFile(); + fileLine++; + + switch (mode) { + case 0: + // Checks if there is anything to read + if (curline == _T("")) continue; + + // Check if it's a line number + if (!curline.IsNumber()) { + Clear(); + throw wxString::Format(_T("Parse error on entry %i at line %i (expecting line number). Possible malformed file."),linen,fileLine); + } + + // Read line number + curline.ToLong(&templ); + if (templ != linen) { + linen = templ; + } + line = new AssDialogue(); + mode = 1; + break; + + case 1: + // Read timestamps + if (curline.substr(13,3) != _T("-->")) { + Clear(); + throw wxString::Format(_T("Parse error on entry %i at line %i (expecting timestamps). Possible malformed file."),linen,fileLine); + } + line->Start.ParseSRT(curline.substr(0,12)); + line->End.ParseSRT(curline.substr(17,12)); + mode = 2; + break; + + case 2: + // Checks if it's done + if (curline == _T("")) { + mode = 0; + linen++; + line->group = _T("[Events]"); + line->Style = _T("Default"); + line->Comment = false; + line->UpdateData(); + line->ParseSRTTags(); + line->StartMS = line->Start.GetMS(); + Line.push_back(line); + break; + } + // Append text + if (line->Text != _T("")) line->Text += _T("\\N"); + line->Text += curline; + break; + } + } +} + + +//////////////////////////// +// Loads TXT subs from disk +void AssFile::LoadTXT (wxString _filename,wxString encoding) { + using namespace std; + + // Reader + TextFileReader file(_filename,encoding,false); + + // Default + LoadDefault(false); + IsASS = false; + + // Data + wxString actor; + wxString separator = Options.AsText(_T("Text actor separator")); + wxString comment = Options.AsText(_T("Text comment starter")); + bool isComment = false; + + // Parse file + AssDialogue *line = NULL; + while (file.HasMoreLines()) { + // Reads line + wxString value = file.ReadLineFromFile(); + + // Read comment data + isComment = false; + if (comment != _T("") && value.Left(comment.Length()) == comment) { + isComment = true; + value = value.Mid(comment.Length()); + } + + // Read actor data + if (!isComment && separator != _T("")) { + if (value[0] != _T(' ') && value[0] != _T('\t')) { + size_t pos = value.Find(separator); + if (pos != -1) { + actor = value.Left(pos); + actor.Trim(false); + actor.Trim(true); + value = value.Mid(pos+1); + value.Trim(false); + } + } + } + + // Trim spaces at start + value.Trim(false); + + // Sets line up + line = new AssDialogue(); + line->group = _T("[Events]"); + line->Style = _T("Default"); + if (isComment) line->Actor = _T(""); + else line->Actor = actor; + if (value == _T("")) { + line->Actor = _T(""); + isComment = true; + } + line->Comment = isComment; + line->Text = value; + line->StartMS = 0; + line->Start.SetMS(0); + line->End.SetMS(0); + line->UpdateData(); + line->ParseASSTags(); + + // Adds line + Line.push_back(line); + } +} + + +///////////////////////////// +// Chooses format to save in +void AssFile::Save(wxString _filename,bool setfilename,bool addToRecent,const wxString encoding) { + // Finds last dot + int i = 0; + for (i=(int)_filename.size();--i>=0;) { + if (_filename[i] == '.') break; + } + wxString extension = _filename.substr(i+1); + extension.Lower(); + + // ASS + if (extension == _T("ass")) { + SaveASS(_filename,setfilename,encoding); + if (addToRecent) AddToRecent(_filename); + return; + } + + // SSA + if (extension == _T("ssa")) { + AssFile SSA(*this); + SSA.SaveSSA(_filename,encoding); + if (addToRecent) AddToRecent(_filename); + return; + } + + // SRT + if (extension == _T("srt")) { + AssFile SRT(*this); + SRT.SaveSRT(_filename,encoding); + if (addToRecent) AddToRecent(_filename); + return; + } + + // Unknown + throw _T("Unknown file type"); +} + + +//////////////////////////////////////////// +// Exports file with proper transformations +void AssFile::Export(wxString _filename) { + AssExporter exporter(this); + exporter.AddAutoFilters(); + exporter.Export(_filename); +} + + +///////////////////// +// Saves ASS to disk +void AssFile::SaveASS (wxString _filename,bool setfilename,const wxString encoding) { + // Open file + TextFileWriter file(_filename,encoding); + + // Write lines + using std::list; + for (list::iterator cur=Line.begin();cur!=Line.end();cur++) { + file.WriteLineToFile((*cur)->data); + } + + // Done + if (setfilename) { + Modified = false; + filename = _filename; + IsASS = true; + } +} + + +///////////////////// +// Saves SSA to disk +void AssFile::SaveSSA (wxString _filename,const wxString encoding) { + // Open file + TextFileWriter file(_filename,encoding); + + // Convert to SSA + ConvertToSSA(); + + // Write lines + using std::list; + for (list::iterator cur=Line.begin();cur!=Line.end();cur++) { + file.WriteLineToFile((*cur)->GetSSAText()); + } +} + + +////////////////// +// Convert to SSA +void AssFile::ConvertToSSA () { + +} + + +//////////////////// +// Save SRT to disk +// ---------------- +// Note that this function will convert the whole AssFile to SRT +// +void AssFile::SaveSRT (wxString _filename,const wxString encoding) { + // Open file + TextFileWriter file(_filename,encoding); + + // Convert to SRT + ConvertToSRT(); + + // Write lines + int i=1; + using std::list; + for (list::iterator cur=Line.begin();cur!=Line.end();cur++) { + AssDialogue *current = AssEntry::GetAsDialogue(*cur); + if (current) { + // Get line + if (current->Comment) throw _T("Unexpected line type (comment)"); + + // Write line + file.WriteLineToFile(wxString::Format(_T("%i"),i)); + file.WriteLineToFile(current->Start.GetSRTFormated() + _T(" --> ") + current->End.GetSRTFormated()); + file.WriteLineToFile(current->Text); + file.WriteLineToFile(_T("")); + + i++; + } + else throw _T("Unexpected line type"); + } +} + + +/////////////////////// +// Convert line to SRT +void AssFile::DialogueToSRT(AssDialogue *current,std::list::iterator prev) { + using std::list; + AssDialogue *previous; + if (prev != NULL) previous = AssEntry::GetAsDialogue(*prev); + else previous = NULL; + + // Strip ASS tags + current->ConvertTagsToSRT(); + + // Join equal lines + if (previous != NULL) { + if (previous->Text == current->Text) { + if (abs(current->Start.GetMS() - previous->End.GetMS()) < 20) { + current->Start = (current->Start < previous->Start ? current->Start : previous->Start); + current->End = (current->End > previous->End ? current->End : previous->End); + delete *prev; + Line.erase(prev); + } + } + } + + // Fix line breaks + size_t cur = 0; + while ((cur = current->Text.find(_T("\\n"),cur)) != wxString::npos) { + current->Text.replace(cur,2,_T("\r\n")); + } + cur = 0; + while ((cur = current->Text.find(_T("\\N"),cur)) != wxString::npos) { + current->Text.replace(cur,2,_T("\r\n")); + } + cur = 0; + while ((cur = current->Text.find(_T("\r\n\r\n"),cur)) != wxString::npos) { + current->Text.replace(cur,2,_T("\r\n")); + cur = 0; + } +} + + +////////////////////////////// +// Converts whole file to SRT +void AssFile::ConvertToSRT () { + using std::list; + list::iterator next; + list::iterator prev = NULL; + + // Sort lines + Line.sort(LessByPointedToValue()); + + // Process lines + bool notfirst = false; + for (list::iterator cur=Line.begin();cur!=Line.end();cur=next) { + next = cur; + next++; + + // Dialogue line (not comment) + AssDialogue *current = AssEntry::GetAsDialogue(*cur); + if (current && !current->Comment) { + DialogueToSRT(current,prev); + notfirst = true; + prev = cur; + } + + // Other line, delete it + else { + delete *cur; + Line.erase(cur); + } + } +} + + +/////////////////////// +// Appends line to Ass +int AssFile::AddLine (wxString data,wxString group,int lasttime,bool &IsSSA) { + AssEntry *entry = NULL; + + // Dialogue + if (group == _T("[Events]")) { + if ((data.Left(9) == _T("Dialogue:") || data.Left(8) == _T("Comment:"))) { + AssDialogue *diag = new AssDialogue(data,IsSSA); + lasttime = diag->Start.GetMS(); + diag->ParseASSTags(); + entry = diag; + entry->StartMS = lasttime; + entry->group = group; + } + if (data.Left(7) == _T("Format:")) { + entry = new AssEntry(_T("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text")); + entry->StartMS = lasttime; + entry->group = group; + } + } + + // Style + else if (group == _T("[V4+ Styles]")) { + if (data.Left(6) == _T("Style:")) { + AssStyle *style = new AssStyle(data,IsSSA); + entry = style; + entry->StartMS = lasttime; + entry->group = group; + } + if (data.Left(7) == _T("Format:")) { + entry = new AssEntry(_T("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding")); + entry->StartMS = lasttime; + entry->group = group; + } + } + + // Comment in script info + else if (group == _T("[Script Info]")) { + if (data.Left(1) == _T(";")) { + // Skip stupid comments added by other programs + // Of course, we add our own in place... + return lasttime; + } + if (data.Left(11) == _T("ScriptType:")) { + wxString version = data.Mid(11); + version.Trim(true); + version.Trim(false); + version.MakeLower(); + bool trueSSA; + if (version == _T("v4.00")) trueSSA = true; + else if (version == _T("v4.00+")) trueSSA = false; + else throw _T("Unknown file version"); + if (trueSSA != IsSSA) { + wxLogMessage(_T("Warning: File has the wrong extension.")); + IsSSA = trueSSA; + } + } + entry = new AssEntry(data); + entry->StartMS = lasttime; + entry->group = group; + } + + // Common entry + if (entry == NULL) { + entry = new AssEntry(data); + entry->StartMS = lasttime; + entry->group = group; + } + + Line.push_back(entry); + return lasttime; +} + + +////////////////////////////// +// Clears contents of assfile +void AssFile::Clear () { + for (std::list::iterator cur=Line.begin();cur != Line.end();cur++) { + if (*cur) delete *cur; + } + Line.clear(); + + IsASS = false; + loaded = false; + filename = _T(""); + Modified = false; +} + + +////////////////////// +// Loads default subs +void AssFile::LoadDefault (bool defline) { + // Clear first + Clear(); + + // Write headers + AssStyle defstyle; + bool IsSSA = false; + AddLine(_T("[Script Info]"),_T("[Script Info]"),-1,IsSSA); + AddLine(_T("Title: Default Aegisub file"),_T("[Script Info]"),-1,IsSSA); + AddLine(_T("ScriptType: v4.00+"),_T("[Script Info]"),-1,IsSSA); + AddLine(_T("PlayResX: 640"),_T("[Script Info]"),-1,IsSSA); + AddLine(_T("PlayResY: 480"),_T("[Script Info]"),-1,IsSSA); + AddLine(_T(""),_T("[Script Info]"),-1,IsSSA); + AddLine(_T("[V4+ Styles]"),_T("[V4+ Styles]"),-1,IsSSA); + AddLine(_T("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"),_T("[V4+ Styles]"),-1,IsSSA); + AddLine(defstyle.data,_T("[V4+ Styles]"),-1,IsSSA); + AddLine(_T(""),_T("[V4+ Styles]"),-1,IsSSA); + AddLine(_T("[Events]"),_T("[Events]"),-1,IsSSA); + AddLine(_T("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"),_T("[Events]"),-1,IsSSA); + + if (defline) { + AssDialogue def; + AddLine(def.data,_T("[Events]"),0,IsSSA); + } + + loaded = true; + IsASS = true; +} + + +//////////////////// +// Copy constructor +AssFile::AssFile (AssFile &from) { + using std::list; + + // Copy standard variables + filename = from.filename; + IsASS = from.IsASS; + loaded = from.loaded; + Modified = from.Modified; + bool IsSSA = false; + + // Copy lines + int lasttime = -1; + for (list::iterator cur=from.Line.begin();cur!=from.Line.end();cur++) { + lasttime = AddLine((*cur)->data,(*cur)->group,lasttime,IsSSA); + } + + // Add comments + AddComment(_T("Script generated by Aegisub")); + AddComment(_T("http://www.aegisub.net")); +} + + +////////////////////// +// Insert a new style +void AssFile::InsertStyle (AssStyle *style) { + // Variables + using std::list; + AssEntry *curEntry; + list::iterator lastStyle = NULL; + list::iterator cur; + int lasttime; + wxString lastGroup; + + // Look for insert position + for (cur=Line.begin();cur!=Line.end();cur++) { + curEntry = *cur; + if (curEntry->Type == ENTRY_STYLE || (lastGroup == _T("[V4+ Styles]") && curEntry->data.substr(0,7) == _T("Format:"))) { + lastStyle = cur; + } + lasttime = curEntry->StartMS; + lastGroup = curEntry->group; + } + + // No styles found, add them + if (lastStyle == NULL) { + // Add space + curEntry = new AssEntry(_T("")); + curEntry->group = lastGroup; + curEntry->StartMS = lasttime; + Line.push_back(curEntry); + + // Add header + curEntry = new AssEntry(_T("[V4+ Styles]")); + curEntry->group = _T("[V4+ Styles]"); + curEntry->StartMS = lasttime; + Line.push_back(curEntry); + + // Add format line + curEntry = new AssEntry(_T("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding")); + curEntry->group = _T("[V4+ Styles]"); + curEntry->StartMS = lasttime; + Line.push_back(curEntry); + + // Add style + style->group = _T("[V4+ Styles]"); + style->StartMS = lasttime; + Line.push_back(style); + } + + // Add to end of list + else { + lastStyle++; + style->group = (*lastStyle)->group; + style->StartMS = lasttime; + Line.insert(lastStyle,style); + } +} + + +//////////////////// +// Gets script info +wxString AssFile::GetScriptInfo(const wxString _key) { + // Prepare + wxString key = _key;; + key.Lower(); + key += _T(":"); + std::list::iterator cur; + bool GotIn = false; + + // Look for it + for (cur=Line.begin();cur!=Line.end();cur++) { + if ((*cur)->group == _T("[Script Info]")) { + GotIn = true; + wxString curText = (*cur)->data; + curText.Lower(); + + // Found + if (curText.Left(key.length()) == key) { + wxString result = curText.Mid(key.length()); + result.Trim(false); + result.Trim(true); + return result; + } + } + else if (GotIn) break; + } + + // Couldn't find + return _T(""); +} + + +////////////////////////// +// Get script info as int +int AssFile::GetScriptInfoAsInt(const wxString key) { + long temp = 0; + try { + GetScriptInfo(key).ToLong(&temp); + } + catch (...) { + temp = 0; + } + return temp; +} + + +////////////////////////// +// Set a script info line +void AssFile::SetScriptInfo(const wxString _key,const wxString value) { + // Prepare + wxString key = _key;; + key.Lower(); + key += _T(":"); + std::list::iterator cur; + std::list::iterator prev; + bool GotIn = false; + + // Look for it + for (cur=Line.begin();cur!=Line.end();cur++) { + if ((*cur)->group == _T("[Script Info]")) { + GotIn = true; + wxString curText = (*cur)->data; + curText.Lower(); + + // Found + if (curText.Left(key.length()) == key) { + // Set value + if (value != _T("")) { + wxString result = _key; + result += _T(": "); + result += value; + (*cur)->data = result; + } + + // Remove key + else { + Line.erase(cur); + return; + } + return; + } + + if (!(*cur)->data.empty()) prev = cur; + } + + // Add + else if (GotIn) { + if (value != _T("")) { + wxString result = _key; + result += _T(": "); + result += value; + AssEntry *entry = new AssEntry(result); + entry->group = (*prev)->group; + entry->StartMS = (*prev)->StartMS; + Line.insert(++prev,entry); + } + return; + } + } +} + + +/////////////////////////////////// +// Adds a comment to [Script Info] +void AssFile::AddComment(const wxString _comment) { + wxString comment = _T("; "); + comment += _comment; + std::list::iterator cur; + int step = 0; + + // Find insert position + for (cur=Line.begin();cur!=Line.end();cur++) { + // Start of group + if (step == 0 && (*cur)->group == _T("[Script Info]")) step = 1; + + // First line after a ; + else if (step == 1 && (*cur)->data.Left(1) != _T(";")) { + AssEntry *prev = *cur; + AssEntry *comm = new AssEntry(comment); + comm->group = prev->group; + comm->StartMS = prev->StartMS; + Line.insert(cur,comm); + break; + } + } +} + + +////////////////////// +// Get list of styles +wxArrayString AssFile::GetStyles() { + wxArrayString styles; + AssStyle *curstyle; + for (entryIter cur=Line.begin();cur!=Line.end();cur++) { + curstyle = AssEntry::GetAsStyle(*cur); + if (curstyle) { + styles.Add(curstyle->name); + } + if (!curstyle && (*cur)->data.Left(5) == _T("Style")) { + wxLogMessage(_T("Style ignored: ") + (*cur)->data); + curstyle = AssEntry::GetAsStyle(*cur); + } + } + if (styles.GetCount() == 0) styles.Add(_T("Default")); + return styles; +} + + +/////////////////////////////// +// Gets style of specific name +AssStyle *AssFile::GetStyle(wxString name) { + AssStyle *curstyle; + for (entryIter cur=Line.begin();cur!=Line.end();cur++) { + curstyle = AssEntry::GetAsStyle(*cur); + if (curstyle) { + if (curstyle->name == name) return curstyle; + } + } + return NULL; +} + + +//////////////////////////////////// +// Adds file name to list of recent +void AssFile::AddToRecent(wxString file) { + Options.AddToRecentList(file,_T("Recent sub")); +} + + +////////////////////////////// +// Checks if file is modified +bool AssFile::IsModified() { + return Modified; +} + + +///////////////////////// +// Flag file as modified +void AssFile::FlagAsModified() { + Modified = true; + StackPush(); +} + + +////////////// +// Stack push +void AssFile::StackPush() { + // Places copy on stack + AssFile *curcopy = new AssFile(*top); + SubsStack.push_back(curcopy); + StackModified = true; + + // Cap depth + int n = 0; + for (std::list::iterator cur=SubsStack.begin();cur!=SubsStack.end();cur++) { + n++; + } + int depth = Options.AsInt(_T("Undo levels")); + while (n > depth) { + delete SubsStack.front(); + SubsStack.pop_front(); + n--; + } +} + + +///////////// +// Stack pop +void AssFile::StackPop() { + bool addcopy = false; + if (StackModified) { + SubsStack.pop_back(); + StackModified = false; + addcopy = true; + } + + if (!SubsStack.empty()) { + delete top; + top = SubsStack.back(); + SubsStack.pop_back(); + Popping = true; + } + + if (addcopy) { + StackPush(); + } +} + + +/////////////// +// Stack clear +void AssFile::StackClear() { + for (std::list::iterator cur=SubsStack.begin();cur!=SubsStack.end();cur++) { + delete *cur; + } + SubsStack.clear(); + Popping = false; +} + + +/////////////// +// Stack reset +void AssFile::StackReset() { + StackClear(); + delete top; + top = new AssFile; + StackModified = false; +} + + +///////////////////////////// +// Returns if stack is empty +bool AssFile::StackEmpty() { + if (StackModified) return (SubsStack.size() <= 1); + else return SubsStack.empty(); +} + + +////////// +// Global +AssFile *AssFile::top; +std::list AssFile::SubsStack; +bool AssFile::Popping; +bool AssFile::StackModified; + diff --git a/core/ass_file.h b/core/ass_file.h new file mode 100644 index 000000000..a2157a098 --- /dev/null +++ b/core/ass_file.h @@ -0,0 +1,136 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +#pragma once + + +/////////// +// Headers +#include +#include +#include + + +////////////// +// Prototypes +class FrameRate; +class AssDialogue; +class AssStyle; +class AssDialogueBlock; +class AssDialogueBlockOverride; +class AssDialogueBlockPlain; +class AssEntry; + + +////////////////////////////////////// +// Class to store the actual ass file +class AssFile { +private: + bool Modified; + + // Stack operations + static std::list SubsStack; + static bool StackModified; + static void StackClear(); + + // I/O operations + void LoadASS(const wxString file,const wxString encoding,bool IsSSA=false); + void LoadSRT(const wxString file,const wxString encoding); + void LoadTXT(const wxString file,const wxString encoding); + void SaveASS(const wxString file,bool setfilename,const wxString encoding=_T("")); + void SaveSSA(const wxString file,const wxString encoding=_T("")); + void SaveSRT(const wxString file,const wxString encoding=_T("")); + + // Manipulation operations + int AddLine(wxString data,wxString group,int lasttime,bool &IsSSA); + void DialogueToSRT(AssDialogue *current,std::list::iterator prev); + + // Format conversion operations + void ConvertToSRT(); + void ConvertToSSA(); + +public: + std::list Line; + + wxString filename; + bool loaded; + bool IsASS; + + AssFile(); + AssFile(AssFile &from); + ~AssFile(); + + bool IsModified(); // Returns if file has unmodified changes + void FlagAsModified(); // Flag file as being modified, will automatically put a copy on stack + void Clear(); // Wipes file + void LoadDefault(bool noline=true); // Loads default file. Pass true to prevent it from adding a default line too + void InsertStyle(AssStyle *style); // Inserts a style to file + wxArrayString GetStyles(); // Gets a list of all styles available + AssStyle *GetStyle(wxString name); // Gets style by its name + + void Load(wxString file,wxString charset=_T("")); // Load from a file + void Save(wxString file,bool setfilename=false,bool addToRecent=true,const wxString encoding=_T("")); // Save to a file. Pass true to second argument if this isn't a copy + void Export(wxString file); // Saves exported copy, with effects applied + void AddToRecent(wxString file); // Adds file name to list of recently opened files + + int GetScriptInfoAsInt(const wxString key); + wxString GetScriptInfo(const wxString key); // Returns the value in a [Script Info] key. + void SetScriptInfo(const wxString key,const wxString value); // Sets the value of a [Script Info] key. Adds it if it doesn't exist. + void AddComment(const wxString comment); // Adds a ";" comment under [Script Info]. + + static void StackPop(); // Pop subs from stack and sets 'top' to it + static void StackPush(); // Puts a copy of 'top' on the stack + static void StackReset(); // Resets stack. Do this before loading new subtitles. + static bool StackEmpty(); // Checks if stack is empty + static bool Popping; // Flags the stack as popping. You must unset this after popping + static AssFile *top; // Current script file. It is "above" the stack. +}; + + +//////////// +// Typedefs +typedef std::list::iterator entryIter; + + +////////////////////////////////////////////////////// +// Hack to get STL sort to work on a list of pointers +template +class LessByPointedToValue : std::binary_function { +public: + bool operator()(T const * x, T const * y) const { + return std::less()(*x, *y); + } +}; diff --git a/core/ass_override.cpp b/core/ass_override.cpp new file mode 100644 index 000000000..3abe84dc2 --- /dev/null +++ b/core/ass_override.cpp @@ -0,0 +1,691 @@ +// Copyright (c) 2005, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +//////////// +// Includes +#include "ass_dialogue.h" +#include "ass_override.h" +#include + + +////////////////////// AssOverrideParameter ////////////////////// +/////////////// +// Constructor +AssOverrideParameter::AssOverrideParameter () { + classification = PARCLASS_NORMAL; + ommited = false; +} + + +////////////// +// Destructor +AssOverrideParameter::~AssOverrideParameter () { +} + + +//////// +// Copy +void AssOverrideParameter::CopyFrom (const AssOverrideParameter ¶m) { + switch(param.GetType()) { + case VARDATA_INT: SetInt(param.AsInt()); break; + case VARDATA_FLOAT: SetFloat(param.AsFloat()); break; + case VARDATA_TEXT: SetText(param.AsText()); break; + case VARDATA_BOOL: SetBool(param.AsBool()); break; + case VARDATA_COLOUR: SetColour(param.AsColour()); break; + case VARDATA_BLOCK: SetBlock(param.AsBlock()); break; + default: DeleteValue(); + } + classification = param.classification; + ommited = param.ommited; +} + +void AssOverrideParameter::operator= (const AssOverrideParameter ¶m) { + CopyFrom(param); +} + + +////////////////////// AssDialogueBlockOverride ////////////////////// +/////////////// +// Constructor +AssDialogueBlockOverride::AssDialogueBlockOverride () { + type = BLOCK_OVERRIDE; +} + + +////////////// +// Destructor +AssDialogueBlockOverride::~AssDialogueBlockOverride () { + for (std::vector::iterator cur=Tags.begin();cur!=Tags.end();cur++) { + delete *cur; + } + Tags.clear(); +} + + +///////////// +// Read tags +void AssDialogueBlockOverride::ParseTags () { + // Clear current vector + Tags.clear(); + + // Fix parenthesis matching + while (text.Freq(_T('(')) > text.Freq(_T(')'))) { + text += _T(")"); + } + + // Initialize tokenizer + wxStringTokenizer tkn(text,_T("\\"),wxTOKEN_RET_EMPTY_ALL); + + while (tkn.HasMoreTokens()) { + wxString curTag = _T("\\"); + curTag += tkn.GetNextToken(); + if (curTag == _T("\\")) continue; + + // Check for parenthesis matching + while (curTag.Freq(_T('(')) > curTag.Freq(_T(')'))) { + if (!tkn.HasMoreTokens()) { + wxLogWarning(_T("Unmatched parenthesis! Line contents: ") + parent->Text); + break; + } + curTag << _T("\\") << tkn.GetNextToken(); + } + + AssOverrideTag *newTag = new AssOverrideTag; + newTag->SetText(curTag); + Tags.push_back(newTag); + } +} + + +/////////////////////////// +// Get Text representation +wxString AssDialogueBlockOverride::GetText () { + text = _T(""); + for (std::vector::iterator cur=Tags.begin();cur!=Tags.end();cur++) { + text += (*cur)->ToString(); + } + return text; +} + + +/////////////////////////////////// +// Process parameters via callback +void AssDialogueBlockOverride::ProcessParameters(void (*callback)(wxString,int,AssOverrideParameter *,void *),void *userData) { + AssOverrideTag *curTag; + AssOverrideParameter *curPar; + + // Find tags + for (std::vector::iterator cur=Tags.begin();cur!=Tags.end();cur++) { + int n = 0; + curTag = *cur; + + // Find parameters + for (std::vector::iterator curParam=curTag->Params.begin();curParam!=curTag->Params.end();curParam++) { + curPar = *curParam; + + if (curPar->GetType() != VARDATA_NONE && curPar->ommited == false) { + // Do callback + (*callback)(curTag->Name,n,curPar,userData); + + // Go recursive if it's a block parameter + //if (curPar->classification == VARDATA_BLOCK) { + if (curPar->GetType() == VARDATA_BLOCK) { + curPar->AsBlock()->ProcessParameters(callback,userData); + } + } + + n++; + } + } +} + + +///////////////////////// AssOverrideParamProto ////////////////////////// +/////////////// +// Constructor +AssOverrideParamProto::AssOverrideParamProto (VariableDataType _type,int opt,ASS_ParameterClass classi) { + type = _type; + optional = opt; + classification = classi; +} + + +////////////// +// Destructor +AssOverrideParamProto::~AssOverrideParamProto() { +} + + +///////////////////////// AssOverrideTagProto ////////////////////////// +/////////////// +// Static vars +std::list AssOverrideTagProto::proto; +bool AssOverrideTagProto::loaded = false; + + +/////////////// +// Constructor +AssOverrideTagProto::AssOverrideTagProto() { +} + + +////////////// +// Destructor +AssOverrideTagProto::~AssOverrideTagProto() { +} + + +/////////////////// +// Load prototypes +void AssOverrideTagProto::LoadProtos () { + if (loaded) return; + loaded = true; + + // \alpha + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\alpha"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT)); + + // \bord + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\bord"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_SIZE)); + + // \shad + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\shad"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_SIZE)); + + // \fade(,,,,,,) + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\fade"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_RELATIVE_TIME_START)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_RELATIVE_TIME_START)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_RELATIVE_TIME_START)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_RELATIVE_TIME_START)); + + // \move(,,,[,,]) + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\move"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_X)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_X)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,OPTIONAL_6,PARCLASS_RELATIVE_TIME_START)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,OPTIONAL_6,PARCLASS_RELATIVE_TIME_START)); + + // \clip(,,,) + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\clip"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_X)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_X)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y)); + + // \clip([,]) + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\clip"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,OPTIONAL_2,PARCLASS_NORMAL)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_DRAWING)); + + // \fscx + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\fscx"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_RELATIVE_SIZE_X)); + + // \fscy + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\fscy"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_RELATIVE_SIZE_Y)); + + // \pos(,) + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\pos"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_X)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y)); + + // \org(,) + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\org"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_X)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y)); + + // \pbo + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\pbo"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_POS_Y)); + + // \fad(,) + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\fad"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_RELATIVE_TIME_START)); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_RELATIVE_TIME_END)); + + // \fsp + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\fsp"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_SIZE)); + + // \frx + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\frx"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \fry + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\fry"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \frz + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\frz"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \fr + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\fr"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_FLOAT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \1c&H& + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\1c"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \2c&H& + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\2c"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \3c&H& + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\3c"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \4c&H& + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\4c"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \1a&H& + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\1a"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \2a&H& + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\2a"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \3a&H& + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\3a"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \4a&H& + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\4a"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \fe + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\fe"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \ko + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\ko"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_KARAOKE)); + + // \kf + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\kf"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_KARAOKE)); + + // \be<0/1> + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\be"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_BOOL,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \fn + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\fn"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \fs + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\fs"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_ABSOLUTE_SIZE)); + + // \an + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\an"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \c&H& + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\c"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \b<0/1/weight> + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\b"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,OPTIONAL_1,PARCLASS_NORMAL)); + proto.back().params.back().defaultValue.SetBool(false); + + // \i<0/1> + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\i"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_BOOL,OPTIONAL_1,PARCLASS_NORMAL)); + proto.back().params.back().defaultValue.SetBool(false); + + // \u<0/1> + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\u"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_BOOL,OPTIONAL_1,PARCLASS_NORMAL)); + proto.back().params.back().defaultValue.SetBool(false); + + // \s<0/1> + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\s"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_BOOL,OPTIONAL_1,PARCLASS_NORMAL)); + proto.back().params.back().defaultValue.SetBool(false); + + // \a + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\a"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \k + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\k"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_KARAOKE)); + + // \K + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\K"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_KARAOKE)); + + // \q<0-3> + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\q"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \p + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\p"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_INT,NOT_OPTIONAL,PARCLASS_NORMAL)); + + // \r[] + proto.push_back(AssOverrideTagProto()); + proto.back().name = _T("\\r"); + proto.back().params.push_back(AssOverrideParamProto(VARDATA_TEXT,OPTIONAL_1,PARCLASS_NORMAL)); + + // \t([,,][,]