1# $Id: Subtitle.pm 2344 2007-08-09 21:37:41Z joern $
2
3#-----------------------------------------------------------------------
4# Copyright (C) 2001-2006 J�rn Reder <joern AT zyn.de>.
5# All Rights Reserved. See file COPYRIGHT for details.
6#
7# This module is part of Video::DVDRip, which is free software; you can
8# redistribute it and/or modify it under the same terms as Perl itself.
9#-----------------------------------------------------------------------
10
11package Video::DVDRip::GUI::Project::Subtitle;
12
13use base qw( Video::DVDRip::GUI::Base );
14
15use Locale::TextDomain qw (video.dvdrip);
16
17use Carp;
18use strict;
19
20use Video::DVDRip::GUI::FormFactory::SubtitlePreviews;
21
22sub new {
23    my $class = shift;
24
25    my $self = $class->SUPER::new(@_);
26
27    return $self;
28}
29
30sub build_factory {
31    my $self = shift;
32
33    my $context = $self->get_context;
34
35    $context->set_object( subtitle_gui => $self );
36
37    return Gtk2::Ex::FormFactory::VBox->new(
38        $self->get_optimum_screen_size_options("page"),
39        title       => '[gtk-underline]'.__ "Subtitles",
40        object      => "title",
41        active_cond => sub {
42            $self->selected_title
43                && $self->project
44                && $self->project->created;
45        },
46        active_depends => "project.created",
47        no_frame       => 1,
48        content        => [
49            Video::DVDRip::GUI::Main->build_selected_title_factory,
50            Gtk2::Ex::FormFactory::VBox->new(
51                object   => "subtitle",
52                inactive => "invisible",
53                expand   => 1,
54                content  => [
55                    $self->build_select_box, $self->build_preview_box,
56                    $self->build_render_box, $self->build_vobsub_box,
57                ],
58            ),
59            Gtk2::Ex::FormFactory::Label->new(
60                label => "\n<b>" . __("This title has no subtitles") . "</b>",
61                with_markup => 1,
62                inactive    => "invisible",
63                active_cond => sub {
64                    !( $self->selected_title && $self->selected_title->has_subtitles )
65                },
66                active_depends => "title",
67            ),
68        ],
69    );
70}
71
72sub build_select_box {
73    my $self = shift;
74
75    return Gtk2::Ex::FormFactory::HBox->new(
76        title   => __ "Subtitle selection",
77        content => [
78            Gtk2::Ex::FormFactory::Popup->new(
79                attr  => "title.selected_subtitle_id",
80                label => __ "Select subtitle",
81            ),
82            Gtk2::Ex::FormFactory::Label->new(
83                attr  => "title.subtitles_activated",
84                label => __ "Activated:",
85            ),
86        ],
87    );
88}
89
90sub build_preview_box {
91    my $self = shift;
92
93    my $preview_event_box = Gtk2::EventBox->new;
94    $preview_event_box->modify_bg( "normal",
95        Gtk2::Gdk::Color->parse("#ffffff") );
96
97    return Gtk2::Ex::FormFactory::VBox->new(
98        title   => __ "Preview",
99        expand  => 1,
100        content => [
101            Gtk2::Ex::FormFactory::HBox->new(
102                content => [
103                    Gtk2::Ex::FormFactory::Button->new(
104                        name         => "subtitle_grab_button",
105                        object       => "subtitle",
106                        label        => __ "Grab",
107                        stock        => "gtk-copy",
108                        clicked_hook => sub {
109                            $self->grab_subtitle_preview_images;
110                        },
111                        inactive    => "invisible",
112                        active_cond => sub {
113                            my ($subtitle) = @_;
114                            return 0 unless $subtitle;
115                            return 0 if     $subtitle->is_ripped;
116                            return $self->progress_is_active ?
117                                'insensitive' : 'sensitive';
118                        },
119                        active_depends => ["subtitle.is_ripped", "progress.is_active" ],
120                    ),
121                    Gtk2::Ex::FormFactory::Combo->new(
122                        attr    => "subtitle.tc_preview_img_cnt",
123                        presets => [ 1, 3, 5, 10, 20, 30, 50, 100 ],
124                        width   => 60,
125                        rules   => "positive-integer",
126                    ),
127                    Gtk2::Ex::FormFactory::Label->new(
128                        label => __ "image(s)",
129                    ),
130                    Gtk2::Ex::FormFactory::Entry->new(
131                        attr  => "subtitle.tc_preview_timecode",
132                        label => __ "starting at",
133                        width => 60,
134                        rules => sub {
135                            my ($value) = @_;
136                            my $frames = $self->selected_title->frames;
137                            return
138                                if $value =~ /^\d+$/
139                                and $value <= $frames;
140                            return if $value =~ /^\d\d:\d\d:\d\d$/;
141                            return __x( "Movie has only {cnt} frames",
142                                cnt => $frames )
143                                if $value =~ /^\d+$/
144                                and $value > $frames;
145                            return __ "Invalid time/frame number format";
146                        },
147                    ),
148                    Gtk2::Ex::FormFactory::Label->new(
149                        label => __ "(timecode nn:nn:nn or frame number)",
150                    ),
151                ],
152            ),
153            Video::DVDRip::GUI::FormFactory::SubtitlePreviews->new(
154                attr            => "subtitle.preview_dir",
155                attr_image_cnt  => "subtitle.tc_preview_img_cnt",
156                attr_start_time => "subtitle.tc_preview_timecode",
157                expand          => 1,
158            ),
159        ],
160    );
161}
162
163sub build_render_box {
164    my $self = shift;
165
166    my $context = $self->get_context;
167
168    return Gtk2::Ex::FormFactory::Table->new(
169        object      => "subtitle",
170        active_cond =>
171            sub { $context->get_object_attr("title.subtitle_render_ok") },
172        active_depends => "subtitle.tc_vobsub",
173        title          => __ "Render subtitle on movie",
174        layout         => "
175                +--------+------------+>>>+--------+-----------+>>>+-----------+-------+
176                | ActLab | RendCheckb | S | Colors | ColCheckB | S | PrevImCnt | Entry |
177                +--------+-------+----+   +--------+-----+-----+   +-----------+-------+
178                | VOff   | Entry | Un |   | Gray   | Ent | Ent |   | PrevWin   | Open  |
179                +--------+-------+----+   +--------+-----+-----+   +-----------+-------+
180                | TimeS  | Entry | Un |   | Index  | Pop | Pop |   |                   |
181                +--------+-------+----+   +--------+-----+-----+---+-------------------+
182                | PostPr | Antiaalias |   | Suggst | Letterbox |   | Full Size Movie   |
183                +--------+-------+----+---+--------+-----------+---+-------------------+
184	    ",
185        content => [
186
187            #-- Row #1
188            Gtk2::Ex::FormFactory::Label->new(
189                label       => __ "Activate this subtitle" . " ",
190                label_group => "subtitle1",
191            ),
192            Gtk2::Ex::FormFactory::CheckButton->new(
193                attr  => "subtitle.tc_render",
194                label => __ "for rendering",
195            ),
196            Gtk2::Ex::FormFactory::VSeparator->new,
197            Gtk2::Ex::FormFactory::Label->new(
198                label => __ "Colors" . " ",
199                for   => "subtitle.tc_color_manip",
200            ),
201            Gtk2::Ex::FormFactory::CheckButton->new(
202                attr  => "subtitle.tc_color_manip",
203                label => __ "Enable manipulation",
204            ),
205            Gtk2::Ex::FormFactory::VSeparator->new,
206            Gtk2::Ex::FormFactory::Label->new(
207                label => __ "Preview image count" . " ",
208                for   => "subtitle.tc_test_image_cnt",
209            ),
210            Gtk2::Ex::FormFactory::Entry->new(
211                attr  => "subtitle.tc_test_image_cnt",
212                width => 50,
213                rules => "positive-integer",
214            ),
215
216            #-- Row #2
217            Gtk2::Ex::FormFactory::Label->new(
218                label => __ "Vertical offset" . " ",
219                for   => "subtitle.tc_vertical_offset",
220            ),
221            Gtk2::Ex::FormFactory::Entry->new(
222                attr  => "subtitle.tc_vertical_offset",
223                width => 50,
224                rules => "integer",
225            ),
226            Gtk2::Ex::FormFactory::Label->new(
227                label       => __ "rows",
228                active_cond =>
229                    sub { $context->get_object_attr("subtitle.tc_render") },
230            ),
231            Gtk2::Ex::FormFactory::Label->new(
232                label => __ "Gray A/B" . " ",
233                for   => "subtitle.tc_color_a",
234            ),
235            Gtk2::Ex::FormFactory::Entry->new(
236                attr  => "subtitle.tc_color_a",
237                width => 50,
238                rules => sub {
239                    $_[0] =~ /^\d+$/ && $_[0] >= 0 && $_[0] <= 255
240                        ? undef
241                        : __ "Value not between 0 and 255";
242                },
243            ),
244            Gtk2::Ex::FormFactory::Entry->new(
245                attr  => "subtitle.tc_color_b",
246                width => 50,
247                rules => sub {
248                    $_[0] =~ /^\d+$/ && $_[0] >= 0 && $_[0] <= 255
249                        ? undef
250                        : __ "Value not between 0 and 255";
251                },
252            ),
253            Gtk2::Ex::FormFactory::Label->new(
254                label => __ "Preview window" . " ",
255                active_cond => sub {
256                    return 0 unless $context->get_object_attr("subtitle.tc_render");
257                    return !$self->progress_is_active;
258                },
259                active_depends => [ "subtitle.tc_render", "progress.is_active" ],
260            ),
261            Gtk2::Ex::FormFactory::Button->new(
262                stock       => "gtk-media-play",
263                label       => __ "Open",
264                object      => "subtitle",
265                clicked_hook   => sub { $self->subtitle_preview_window },
266                active_cond => sub {
267                    return 0 unless $context->get_object_attr("subtitle.tc_render");
268                    return !$self->progress_is_active;
269                },
270                active_depends => [ "subtitle.tc_render", "progress.is_active" ],
271            ),
272
273            #-- Row #3
274            Gtk2::Ex::FormFactory::Label->new(
275                label => __ "Time shift" . " ",
276                for   => "subtitle.tc_time_shift",
277            ),
278            Gtk2::Ex::FormFactory::Entry->new(
279                attr  => "subtitle.tc_time_shift",
280                width => 50,
281                rules => "positive-zero-integer",
282            ),
283            Gtk2::Ex::FormFactory::Label->new(
284                label       => __ "ms",
285                active_cond =>
286                    sub { $context->get_object_attr("subtitle.tc_render") },
287                active_depends => "subtitle.tc_render",
288            ),
289            Gtk2::Ex::FormFactory::Label->new(
290                label => __ "Index A/B" . " ",
291                for   => "subtitle.tc_assign_color_a",
292            ),
293            Gtk2::Ex::FormFactory::Popup->new(
294                attr  => "subtitle.tc_assign_color_a",
295                width => 50,
296            ),
297            Gtk2::Ex::FormFactory::Popup->new(
298                attr  => "subtitle.tc_assign_color_b",
299                width => 50,
300            ),
301
302            #-- Row #4
303            Gtk2::Ex::FormFactory::CheckButton->new(
304                attr  => "subtitle.tc_postprocess",
305                label => __ "Postprocessing" . " ",
306            ),
307            Gtk2::Ex::FormFactory::CheckButton->new(
308                attr  => "subtitle.tc_antialias",
309                label => __ "Antialiasing",
310            ),
311            Gtk2::Ex::FormFactory::Label->new(
312                label       => __ "Suggest" . " ",
313                active_cond =>
314                    sub { $context->get_object_attr("subtitle.tc_render") },
315                active_depends => "subtitle.tc_render",
316            ),
317            Gtk2::Ex::FormFactory::Button->new(
318                label       => __ "Values for letterbox",
319                object      => "subtitle",
320                active_cond =>
321                    sub { $context->get_object_attr("subtitle.tc_render") },
322                active_depends => "subtitle.tc_render",
323                clicked_hook   => sub {
324                    $self->suggest_render_black_bars;
325                },
326            ),
327            Gtk2::Ex::FormFactory::Button->new(
328                label       => __ "Values for full size movie",
329                object      => "subtitle",
330                active_cond =>
331                    sub { $context->get_object_attr("subtitle.tc_render") },
332                active_depends => "subtitle.tc_render",
333                clicked_hook   => sub {
334                    $self->suggest_render_full_size;
335                },
336            ),
337        ],
338    );
339}
340
341sub build_vobsub_box {
342    my $self = shift;
343
344    my $context = $self->get_context;
345
346    return Gtk2::Ex::FormFactory::Table->new(
347        object      => "subtitle",
348        active_cond =>
349            sub { !$context->get_object_attr("subtitle.tc_render") },
350        active_depends => "subtitle.tc_render",
351        title          => __ "Create vobsub file",
352        layout         => "
353                +---------+---------+---------+---------------+
354                | Now     | Button  | Button  | Info          |
355                +---------+---------+---------+---------------+
356                | Later   | after transcoding | Info          |
357                +---------+-------------------+---------------+
358	    ",
359        content => [
360
361            #-- Row #1
362            Gtk2::Ex::FormFactory::Label->new(
363                label       => __ "Create now",
364                label_group => "subtitle1",
365            ),
366            Gtk2::Ex::FormFactory::Button->new(
367                label          => __ "Create now",
368                clicked_hook   => sub { $self->create_vobsub_now },
369                active_cond    => sub { !$self->progress_is_active },
370                active_depends => "progress.is_active",
371            ),
372            Gtk2::Ex::FormFactory::Button->new(
373                label        => __ "View vobsub",
374                clicked_hook => sub { $self->view_vobsub },
375            ),
376            Gtk2::Ex::FormFactory::Label->new(
377                label => "      " . __ "Only useful for single-CD-rips or after transcoding",
378            ),
379
380            #-- Row #2
381            Gtk2::Ex::FormFactory::Label->new( label => __ "Create later", ),
382            Gtk2::Ex::FormFactory::CheckButton->new(
383                attr  => "subtitle.tc_vobsub",
384                label => __ "after transcoding",
385            ),
386            Gtk2::Ex::FormFactory::Label->new(
387                      label => "      "
388                    . __ "This considers splitted files automaticly",
389            ),
390        ],
391    );
392}
393
394sub suggest_render_black_bars {
395    my $self = shift;
396
397    my $title = $self->selected_title;
398    return 1 if not $title;
399
400    $title->suggest_subtitle_on_black_bars;
401
402    $self->get_context->get_object("clip_zoom")->make_previews;
403    $self->get_context->update_object_widgets("title");
404    $self->get_context->update_object_widgets("bitrate_calc");
405    $self->get_context->update_object_attr_widgets(
406        "subtitle.tc_vertical_offset");
407
408    1;
409}
410
411sub suggest_render_full_size {
412    my $self = shift;
413
414    my $title = $self->selected_title;
415    return 1 if not $title;
416
417    $title->suggest_subtitle_on_movie;
418
419    $self->get_context->get_object("clip_zoom")->make_previews;
420    $self->get_context->update_object_widgets("title");
421    $self->get_context->update_object_widgets("bitrate_calc");
422    $self->get_context->update_object_attr_widgets(
423        "subtitle.tc_vertical_offset");
424
425    1;
426}
427
428sub create_vobsub_now {
429    my $self = shift;
430
431    my $title = $self->selected_title;
432    return 1 if not $title;
433
434    my $subtitle = $title->selected_subtitle;
435    return 1 if not $subtitle;
436
437    my $job_planner   = $self->get_context->get_object("job_planner");
438    my $exec_flow_gui = $self->get_context->get_object("exec_flow_gui");
439    my $job           = $job_planner->build_vobsub_job($title, $subtitle);
440
441    $exec_flow_gui->start_job($job);
442
443    1;
444}
445
446sub view_vobsub {
447    my $self = shift;
448
449    my $title = $self->selected_title;
450    return 1 if not $title;
451
452    my $subtitle = $title->selected_subtitle;
453    return 1 if not $subtitle;
454
455    if ( not $self->has("mplayer") ) {
456        $self->message_window(
457            message => __ "You need Mplayer to view vobsub files." );
458        return 1;
459    }
460
461    if ( $title->project->rip_mode ne 'rip' ) {
462        $self->message_window(
463            message => __ "This is only supported for ripped movies." );
464        return 1;
465    }
466
467    if ( not $subtitle->vobsub_file_exists ) {
468        $self->message_window(
469            message => __ "What about creating the vobsub file first?" );
470        return 1;
471    }
472
473    my $command = $title->get_view_vobsub_command( subtitle => $subtitle );
474
475    $self->log( __ "Executing command: " . $command );
476
477    system("$command &");
478
479    1;
480}
481
482sub subtitle_preview_window {
483    my $self = shift;
484
485    my $title = $self->selected_title;
486
487    my $orig_preview_start_frame = $title->tc_preview_start_frame;
488    my $orig_preview_end_frame   = $title->tc_preview_end_frame;
489
490    my ( $from, $to ) = $title->get_subtitle_test_frame_range;
491
492    $title->set_tc_preview_start_frame($from);
493    $title->set_tc_preview_end_frame($to);
494
495    my $restore_cb = sub {
496        $title->set_tc_preview_start_frame($orig_preview_start_frame);
497        $title->set_tc_preview_end_frame($orig_preview_end_frame);
498    };
499
500    require Video::DVDRip::GUI::Preview;
501
502    my $preview = Video::DVDRip::GUI::Preview->new(
503        form_factory => $self->get_form_factory,
504        closed_cb    => $restore_cb,
505        eof_cb       => $restore_cb,
506    );
507
508    $preview->open;
509
510    1;
511}
512
513sub grab_subtitle_preview_images {
514    my $self    = shift;
515    my %par     = @_;
516    my ($force) = @par{'force'};
517
518    my $title = $self->selected_title;
519    return 1 if not $title;
520    return 1 if $self->progress_is_active;
521    my $selected_subtitle = $title->selected_subtitle;
522    return 1 if not $selected_subtitle;
523
524    if ( not $title->is_ripped ) {
525        $self->message_window(
526            message => __ "You first have to rip this title." );
527        return 1;
528    }
529
530    if ( not $self->has("subtitle2pgm") ) {
531        $self->message_window(
532            message => __ "Sorry, you need subtitle2pgm for this to work." );
533        return 1;
534    }
535
536    my $job_planner   = $self->get_context->get_object("job_planner");
537    my $exec_flow_gui = $self->get_context->get_object("exec_flow_gui");
538    my $job           = $job_planner->build_grab_subtitle_images_job($title);
539
540    $job->get_post_callbacks->add(
541        sub {
542            $self->get_context->update_object_attr_widgets(
543                "title.selected_subtitle_id");
544            $self->get_context->update_object_attr_widgets(
545                "subtitle.preview_dir");
546        }
547    );
548
549    $exec_flow_gui->start_job($job);
550
551    1;
552}
553
5541;
555