title color fader/tweaks, bg_color bcbitmap fix, inst.sh fix, lang fr pref tweaks
[goodguy/cinelerra.git] / cinelerra-5.1 / cinelerra / edit.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21
22 #include "asset.h"
23 #include "assets.h"
24 #include "bcsignals.h"
25 #include "clip.h"
26 #include "edit.h"
27 #include "edits.h"
28 #include "edl.h"
29 #include "edlsession.h"
30 #include "filexml.h"
31 #include "filesystem.h"
32 #include "localsession.h"
33 #include "plugin.h"
34 #include "mainsession.h"
35 #include "strack.h"
36 #include "trackcanvas.h"
37 #include "tracks.h"
38 #include "transition.h"
39 #include <string.h>
40
41
42 Edit::Edit()
43 {
44         reset();
45 }
46
47 Edit::Edit(EDL *edl, Track *track)
48 {
49         reset();
50         this->edl = edl;
51         this->track = track;
52         if(track) this->edits = track->edits;
53         id = EDL::next_id();
54 }
55
56 Edit::Edit(EDL *edl, Edits *edits)
57 {
58         reset();
59         this->edl = edl;
60         this->edits = edits;
61         if(edits) this->track = edits->track;
62         id = EDL::next_id();
63 }
64
65 Edit::~Edit()
66 {
67 //printf("Edit::~Edit 1\n");
68         if(transition) delete transition;
69 //printf("Edit::~Edit 2\n");
70 }
71
72 void Edit::reset()
73 {
74         edl = 0;
75         track = 0;
76         edits = 0;
77         startsource = 0;
78         startproject = 0;
79         length = 0;
80         asset = 0;
81         transition = 0;
82         channel = 0;
83         user_title[0] = 0;
84         nested_edl = 0;
85         is_plugin = 0;
86         is_selected = 0;
87         hard_left = 0;
88         hard_right = 0;
89         color = 0;
90 }
91
92 Indexable* Edit::get_source()
93 {
94         if(asset) return asset;
95         if(nested_edl) return nested_edl;
96         return 0;
97 }
98
99 int Edit::copy(int64_t start,
100         int64_t end,
101         FileXML *file,
102         const char *output_path)
103 {
104 // variables
105 //printf("Edit::copy 1\n");
106
107         int64_t endproject = startproject + length;
108         int result;
109
110         if((startproject >= start && startproject <= end) ||  // startproject in range
111                  (endproject <= end && endproject >= start) ||     // endproject in range
112                  (startproject <= start && endproject >= end))    // range in project
113         {
114 // edit is in range
115                 int64_t startproject_in_selection = startproject; // start of edit in selection in project
116                 int64_t startsource_in_selection = startsource; // start of source in selection in source
117                 //int64_t endsource_in_selection = startsource + length; // end of source in selection
118                 int64_t length_in_selection = length;             // length of edit in selection
119 //printf("Edit::copy 2\n");
120
121                 if(startproject < start)
122                 {         // start is after start of edit in project
123                         int64_t length_difference = start - startproject;
124
125                         startsource_in_selection += length_difference;
126                         startproject_in_selection += length_difference;
127                         length_in_selection -= length_difference;
128                 }
129 //printf("Edit::copy 3\n");
130
131                 if(endproject > end)
132                 {         // end is before end of edit in project
133                         length_in_selection = end - startproject_in_selection;
134                 }
135
136 //printf("Edit::copy 4\n");
137                 if(file)    // only if not counting
138                 {
139                         file->tag.set_title("EDIT");
140                         file->tag.set_property("STARTSOURCE", startsource_in_selection);
141                         file->tag.set_property("CHANNEL", (int64_t)channel);
142                         file->tag.set_property("LENGTH", length_in_selection);
143                         file->tag.set_property("HARD_LEFT", hard_left);
144                         file->tag.set_property("HARD_RIGHT", hard_right);
145                         file->tag.set_property("COLOR", color);
146                         if(user_title[0]) file->tag.set_property("USER_TITLE", user_title);
147 //printf("Edit::copy 5\n");
148
149                         copy_properties_derived(file, length_in_selection);
150
151                         file->append_tag();
152 //                      file->append_newline();
153 //printf("Edit::copy 6\n");
154
155                         if(nested_edl)
156                         {
157                                 file->tag.set_title("NESTED_EDL");
158                                 file->tag.set_property("SRC", nested_edl->path);
159                                 file->append_tag();
160                                 file->tag.set_title("/NESTED_EDL");
161                                 file->append_tag();
162                                 file->append_newline();
163                         }
164
165                         if(asset)
166                         {
167 //printf("Edit::copy 6 %s\n", asset->path);
168                                 char stored_path[BCTEXTLEN];
169                                 char asset_directory[BCTEXTLEN];
170                                 char output_directory[BCTEXTLEN];
171                                 FileSystem fs;
172
173 //printf("Edit::copy %d %s\n", __LINE__, asset->path);
174                                 fs.extract_dir(asset_directory, asset->path);
175 //printf("Edit::copy %d %s\n", __LINE__, asset->path);
176
177                                 if(output_path)
178                                         fs.extract_dir(output_directory, output_path);
179                                 else
180                                         output_directory[0] = 0;
181 //printf("Edit::copy %s, %s %s, %s\n", asset->path, asset_directory, output_path, output_directory);
182
183                                 if(output_path && !strcmp(asset_directory, output_directory))
184                                         fs.extract_name(stored_path, asset->path);
185                                 else
186                                         strcpy(stored_path, asset->path);
187
188                                 file->tag.set_title("FILE");
189                                 file->tag.set_property("SRC", stored_path);
190                                 file->append_tag();
191                                 file->tag.set_title("/FILE");
192                                 file->append_tag();
193                         }
194
195                         if(transition && startsource_in_selection == startsource)
196                         {
197                                 transition->save_xml(file);
198                         }
199
200 //printf("Edit::copy 7\n");
201                         file->tag.set_title("/EDIT");
202                         file->append_tag();
203                         file->append_newline();
204 //printf("Edit::copy 8\n");
205                 }
206 //printf("Edit::copy 9\n");
207                 result = 1;
208         }
209         else
210         {
211                 result = 0;
212         }
213 //printf("Edit::copy 10\n");
214         return result;
215 }
216
217
218 int64_t Edit::get_source_end(int64_t default_)
219 {
220         return default_;
221 }
222
223 void Edit::insert_transition(char *title)
224 {
225 //printf("Edit::insert_transition this=%p title=%p title=%s\n", this, title, title);
226         delete transition;
227         transition = new Transition(edl, this, title,
228                 track->to_units(edl->session->default_transition_length, 1));
229 }
230
231 void Edit::detach_transition()
232 {
233         if(transition) delete transition;
234         transition = 0;
235 }
236
237 int Edit::silence()
238 {
239         return (track->data_type != TRACK_SUBTITLE ?
240                 asset || nested_edl :
241                 *((SEdit *)this)->get_text()) ? 0 : 1;
242 }
243
244
245 void Edit::copy_from(Edit *edit)
246 {
247         this->nested_edl = edl->nested_edls.get_nested(edit->nested_edl);
248         this->asset = edl->assets->update(edit->asset);
249         this->startsource = edit->startsource;
250         this->startproject = edit->startproject;
251         this->length = edit->length;
252         this->hard_left = edit->hard_left;
253         this->hard_right = edit->hard_right;
254         this->color = edit->color;
255         strcpy (this->user_title, edit->user_title);
256
257         if(edit->transition)
258         {
259                 if(!transition) transition = new Transition(edl,
260                         this,
261                         edit->transition->title,
262                         edit->transition->length);
263                 *this->transition = *edit->transition;
264         }
265         this->channel = edit->channel;
266 }
267
268 void Edit::equivalent_output(Edit *edit, int64_t *result)
269 {
270 // End of edit changed
271         if(startproject + length != edit->startproject + edit->length)
272         {
273                 int64_t new_length = MIN(startproject + length,
274                         edit->startproject + edit->length);
275                 if(*result < 0 || new_length < *result)
276                         *result = new_length;
277         }
278
279         if(
280 // Different nested EDLs
281                 (edit->nested_edl && !nested_edl) ||
282                 (!edit->nested_edl && nested_edl) ||
283 // Different assets
284                 (edit->asset == 0 && asset != 0) ||
285                 (edit->asset != 0 && asset == 0) ||
286 // different transitions
287                 (edit->transition == 0 && transition != 0) ||
288                 (edit->transition != 0 && transition == 0) ||
289 // Position changed
290                 (startproject != edit->startproject) ||
291                 (startsource != edit->startsource) ||
292 // Transition changed
293                 (transition && edit->transition &&
294                         !transition->identical(edit->transition)) ||
295 // Asset changed
296                 (asset && edit->asset &&
297                         !asset->equivalent(*edit->asset, 1, 1, edl)) ||
298 // Nested EDL changed
299                 (nested_edl && edit->nested_edl &&
300                         strcmp(nested_edl->path, edit->nested_edl->path))
301                 )
302         {
303 // Start of edit changed
304                 if(*result < 0 || startproject < *result) *result = startproject;
305         }
306 }
307
308
309 Edit& Edit::operator=(Edit& edit)
310 {
311 //printf("Edit::operator= called\n");
312         copy_from(&edit);
313         return *this;
314 }
315
316 void Edit::synchronize_params(Edit *edit)
317 {
318         copy_from(edit);
319 }
320
321
322 // Comparison for ResourcePixmap drawing
323 int Edit::identical(Edit &edit)
324 {
325         int result = (this->nested_edl == edit.nested_edl &&
326                 this->asset == edit.asset &&
327                 this->startsource == edit.startsource &&
328                 this->startproject == edit.startproject &&
329                 this->length == edit.length &&
330                 this->transition == edit.transition &&
331                 this->channel == edit.channel);
332         return result;
333 }
334
335 int Edit::operator==(Edit &edit)
336 {
337         return identical(edit);
338 }
339
340 double Edit::frames_per_picon()
341 {
342         return Units::round(picon_w()) / frame_w();
343 }
344
345 double Edit::frame_w()
346 {
347         return track->from_units(1) *
348                 edl->session->sample_rate /
349                 edl->local_session->zoom_sample;
350 }
351
352 double Edit::picon_w()
353 {
354         int w = 0, h = 0;
355         if(asset) {
356                 w = asset->width;
357                 h = asset->height;
358         }
359         else if(nested_edl) {
360                 w = nested_edl->session->output_w;
361                 h = nested_edl->session->output_h;
362         }
363         return w>0 && h>0 ? ((double)edl->local_session->zoom_track*w)/h : 0;
364 }
365
366 int Edit::picon_h()
367 {
368         return edl->local_session->zoom_track;
369 }
370
371
372 int Edit::dump(FILE *fp)
373 {
374         fprintf(fp,"     EDIT %p\n", this); fflush(fp);
375         fprintf(fp,"      nested_edl=%p %s asset=%p %s\n",
376                 nested_edl,
377                 nested_edl ? nested_edl->path : "",
378                 asset,
379                 asset ? asset->path : "");
380         fflush(fp);
381         fprintf(fp,"      channel %d, color %08x\n", channel, color);
382         if(transition)
383         {
384                 fprintf(fp,"      TRANSITION %p\n", transition);
385                 transition->dump(fp);
386         }
387         fprintf(fp,"      startsource %jd startproject %jd hard lt/rt %d/%d length %jd\n",
388                 startsource, startproject, hard_left, hard_right, length); fflush(fp);
389         return 0;
390 }
391
392 int Edit::load_properties(FileXML *file, int64_t &startproject)
393 {
394         startsource = file->tag.get_property("STARTSOURCE", (int64_t)0);
395         length = file->tag.get_property("LENGTH", (int64_t)0);
396         hard_left = file->tag.get_property("HARD_LEFT", (int64_t)0);
397         hard_right = file->tag.get_property("HARD_RIGHT", (int64_t)0);
398         color = file->tag.get_property("COLOR", (int64_t)-1);
399         user_title[0] = 0;
400         file->tag.get_property("USER_TITLE", user_title);
401         this->startproject = startproject;
402         load_properties_derived(file);
403         return 0;
404 }
405
406 void Edit::shift(int64_t difference)
407 {
408 //printf("Edit::shift 1 %p %jd %jd\n", this, startproject, difference);
409         startproject += difference;
410 //printf("Edit::shift 2 %jd %jd\n", startproject, difference);
411 }
412
413 int Edit::shift_start_in(int edit_mode,
414         int64_t newposition,
415         int64_t oldposition,
416         int edit_edits,
417         int edit_labels,
418         int edit_plugins,
419         int edit_autos,
420         Edits *trim_edits)
421 {
422         int64_t cut_length = newposition - oldposition;
423         int64_t end_previous_source, end_source;
424
425         if(edit_mode == MOVE_ALL_EDITS)
426         {
427                 if(cut_length < length)
428                 {        // clear partial
429                         edits->clear_recursive(oldposition,
430                                 newposition,
431                                 edit_edits,
432                                 edit_labels,
433                                 edit_plugins,
434                                 edit_autos,
435                                 trim_edits);
436                 }
437                 else
438                 {        // clear entire
439                         edits->clear_recursive(oldposition,
440                                 startproject + length,
441                                 edit_edits,
442                                 edit_labels,
443                                 edit_plugins,
444                                 edit_autos,
445                                 trim_edits);
446                 }
447         }
448         else
449         if(edit_mode == MOVE_ONE_EDIT)
450         {
451 // Paste silence and cut
452 //printf("Edit::shift_start_in 1\n");
453                 if(!previous)
454                 {
455                         Edit *new_edit = edits->create_edit();
456                         new_edit->startproject = this->startproject;
457                         new_edit->length = 0;
458                         edits->insert_before(this,
459                                 new_edit);
460                 }
461 //printf("Edit::shift_start_in 2 %p\n", previous);
462
463                 end_previous_source = previous->get_source_end(previous->startsource + previous->length + cut_length);
464                 if(end_previous_source > 0 &&
465                         previous->startsource + previous->length + cut_length > end_previous_source)
466                         cut_length = end_previous_source - previous->startsource - previous->length;
467
468                 if(cut_length < length)
469                 {               // Move in partial
470                         startproject += cut_length;
471                         startsource += cut_length;
472                         length -= cut_length;
473                         previous->length += cut_length;
474 //printf("Edit::shift_start_in 2\n");
475                 }
476                 else
477                 {               // Clear entire edit
478                         cut_length = length;
479                         previous->length += cut_length;
480                         for(Edit* current_edit = this; current_edit; current_edit = current_edit->next)
481                         {
482                                 current_edit->startproject += cut_length;
483                         }
484                         edits->clear_recursive(oldposition + cut_length,
485                                 startproject + cut_length,
486                                 edit_edits,
487                                 edit_labels,
488                                 edit_plugins,
489                                 edit_autos,
490                                 trim_edits);
491                 }
492 //printf("Edit::shift_start_in 3\n");
493         }
494         else
495         if(edit_mode == MOVE_NO_EDITS)
496         {
497                 end_source = get_source_end(startsource + length + cut_length);
498                 if(end_source > 0 && startsource + length + cut_length > end_source)
499                         cut_length = end_source - startsource - length;
500
501                 startsource += cut_length;
502         }
503         return 0;
504 }
505
506 int Edit::shift_start_out(int edit_mode,
507         int64_t newposition,
508         int64_t oldposition,
509         int edit_edits,
510         int edit_labels,
511         int edit_plugins,
512         int edit_autos,
513         Edits *trim_edits)
514 {
515         int64_t cut_length = oldposition - newposition;
516
517
518         if(asset || nested_edl)
519         {
520                 int64_t end_source = get_source_end(1);
521
522 //printf("Edit::shift_start_out 1 %jd %jd\n", startsource, cut_length);
523                 if(end_source > 0 && startsource < cut_length)
524                 {
525                         cut_length = startsource;
526                 }
527         }
528
529         if(edit_mode == MOVE_ALL_EDITS)
530         {
531 //printf("Edit::shift_start_out 10 %jd\n", cut_length);
532                 startsource -= cut_length;
533                 length += cut_length;
534
535                 if(edit_autos)
536                         edits->shift_keyframes_recursive(startproject,
537                                 cut_length);
538                 if(edit_plugins)
539                         edits->shift_effects_recursive(startproject,
540                                 cut_length,
541                                 edit_autos);
542
543                 for(Edit* current_edit = next; current_edit; current_edit = current_edit->next)
544                 {
545                         current_edit->startproject += cut_length;
546                 }
547         }
548         else
549         if(edit_mode == MOVE_ONE_EDIT)
550         {
551                 if(previous)
552                 {
553                         if(cut_length < previous->length)
554                         {   // Cut into previous edit
555                                 previous->length -= cut_length;
556                                 startproject -= cut_length;
557                                 startsource -= cut_length;
558                                 length += cut_length;
559                         }
560                         else
561                         {   // Clear entire previous edit
562                                 cut_length = previous->length;
563                                 previous->length = 0;
564                                 length += cut_length;
565                                 startsource -= cut_length;
566                                 startproject -= cut_length;
567                         }
568                 }
569         }
570         else
571         if(edit_mode == MOVE_NO_EDITS)
572         {
573                 startsource -= cut_length;
574         }
575
576 // Fix infinite length files
577         if(startsource < 0) startsource = 0;
578         return 0;
579 }
580
581 int Edit::shift_end_in(int edit_mode,
582         int64_t newposition,
583         int64_t oldposition,
584         int edit_edits,
585         int edit_labels,
586         int edit_plugins,
587         int edit_autos,
588         Edits *trim_edits)
589 {
590         int64_t cut_length = oldposition - newposition;
591
592         if(edit_mode == MOVE_ALL_EDITS)
593         {
594 //printf("Edit::shift_end_in 1\n");
595                 if(newposition > startproject)
596                 {        // clear partial edit
597 //printf("Edit::shift_end_in %p %p\n", track->edits, edits);
598                         edits->clear_recursive(newposition,
599                                 oldposition,
600                                 edit_edits,
601                                 edit_labels,
602                                 edit_plugins,
603                                 edit_autos,
604                                 trim_edits);
605                 }
606                 else
607                 {        // clear entire edit
608                         edits->clear_recursive(startproject,
609                                 oldposition,
610                                 edit_edits,
611                                 edit_labels,
612                                 edit_plugins,
613                                 edit_autos,
614                                 trim_edits);
615                 }
616         }
617         else
618         if(edit_mode == MOVE_ONE_EDIT)
619         {
620                 if(next)
621                 {
622                         if(next->asset)
623                         {
624                                 int64_t end_source = next->get_source_end(1);
625
626                                 if(end_source > 0 && next->startsource - cut_length < 0)
627                                 {
628                                         cut_length = next->startsource;
629                                 }
630                         }
631
632                         if(cut_length < length)
633                         {
634                                 length -= cut_length;
635                                 next->startproject -= cut_length;
636                                 next->startsource -= cut_length;
637                                 next->length += cut_length;
638 //printf("Edit::shift_end_in 2 %d\n", cut_length);
639                         }
640                         else
641                         {
642                                 cut_length = length;
643                                 next->length += cut_length;
644                                 next->startsource -= cut_length;
645                                 next->startproject -= cut_length;
646                                 length -= cut_length;
647                         }
648                 }
649                 else
650                 {
651                         if(cut_length < length)
652                         {
653                                 length -= cut_length;
654                         }
655                         else
656                         {
657                                 cut_length = length;
658                                 edits->clear_recursive(startproject,
659                                         oldposition,
660                                         edit_edits,
661                                         edit_labels,
662                                         edit_plugins,
663                                         edit_autos,
664                                         trim_edits);
665                         }
666                 }
667         }
668         else
669 // Does nothing for plugins
670         if(edit_mode == MOVE_NO_EDITS)
671         {
672 //printf("Edit::shift_end_in 3\n");
673                 int64_t end_source = get_source_end(1);
674                 if(end_source > 0 && startsource < cut_length)
675                 {
676                         cut_length = startsource;
677                 }
678                 startsource -= cut_length;
679         }
680         return 0;
681 }
682
683 int Edit::shift_end_out(int edit_mode,
684         int64_t newposition,
685         int64_t oldposition,
686         int edit_edits,
687         int edit_labels,
688         int edit_plugins,
689         int edit_autos,
690         Edits *trim_edits)
691 {
692         int64_t cut_length = newposition - oldposition;
693         int64_t endsource = get_source_end(startsource + length + cut_length);
694
695 // check end of edit against end of source file
696         if(endsource > 0 && startsource + length + cut_length > endsource)
697                 cut_length = endsource - startsource - length;
698
699 //printf("Edit::shift_end_out 1 %jd %d %d %d\n", oldposition, newposition, this->length, cut_length);
700         if(edit_mode == MOVE_ALL_EDITS)
701         {
702 // Extend length
703                 this->length += cut_length;
704
705 // Effects are shifted in length extension
706                 if(edit_plugins)
707                         edits->shift_effects_recursive(oldposition /* startproject */,
708                                 cut_length,
709                                 edit_autos);
710                 if(edit_autos)
711                         edits->shift_keyframes_recursive(oldposition /* startproject */,
712                                 cut_length);
713
714                 for(Edit* current_edit = next; current_edit; current_edit = current_edit->next)
715                 {
716                         current_edit->startproject += cut_length;
717                 }
718         }
719         else
720         if(edit_mode == MOVE_ONE_EDIT)
721         {
722                 if(next)
723                 {
724                         if(cut_length < next->length)
725                         {
726                                 length += cut_length;
727                                 next->startproject += cut_length;
728                                 next->startsource += cut_length;
729                                 next->length -= cut_length;
730 //printf("Edit::shift_end_out %d cut_length=%d\n", __LINE__, cut_length);
731                         }
732                         else
733                         {
734 //printf("Edit::shift_end_out %d cut_length=%d next->length=%d\n", __LINE__, cut_length, next->length);
735                                 cut_length = next->length;
736                                 next->startproject += next->length;
737                                 next->startsource += next->length;
738                                 next->length = 0;
739                                 length += cut_length;
740 //track->dump();
741                         }
742                 }
743                 else
744                 {
745                         length += cut_length;
746                 }
747         }
748         else
749         if(edit_mode == MOVE_NO_EDITS)
750         {
751                 startsource += cut_length;
752         }
753         return 0;
754 }
755
756 int Edit::popup_transition(float view_start, float zoom_units, int cursor_x, int cursor_y)
757 {
758         int64_t left, right, left_unit, right_unit;
759         if(!transition) return 0;
760         get_handle_parameters(left, right, left_unit, right_unit, view_start, zoom_units);
761
762         if(cursor_x > left && cursor_x < right)
763         {
764 //              transition->popup_transition(cursor_x, cursor_y);
765                 return 1;
766         }
767         return 0;
768 }
769
770 int Edit::select_handle(float view_start, float zoom_units, int cursor_x, int cursor_y, int64_t &selection)
771 {
772         int64_t left, right, left_unit, right_unit;
773         get_handle_parameters(left, right, left_unit, right_unit, view_start, zoom_units);
774
775         int64_t pixel1, pixel2;
776         pixel1 = left;
777         pixel2 = pixel1 + 10;
778
779 // test left edit
780 // cursor_x is faked in acanvas
781         if(cursor_x >= pixel1 && cursor_x <= pixel2)
782         {
783                 selection = left_unit;
784                 return 1;     // left handle
785         }
786
787         //int64_t endproject = startproject + length;
788         pixel2 = right;
789         pixel1 = pixel2 - 10;
790
791 // test right edit
792         if(cursor_x >= pixel1 && cursor_x <= pixel2)
793         {
794                 selection = right_unit;
795                 return 2;     // right handle
796         }
797         return 0;
798 }
799
800 void Edit::get_title(char *title)
801 {
802         if( user_title[0] ) {
803                 strcpy(title, user_title);
804                 return;
805         }
806         Indexable *idxbl = asset ? (Indexable*)asset : (Indexable*)nested_edl;
807         if( !idxbl ) {
808                 title[0] = 0;
809                 return;
810         }
811         FileSystem fs;
812         fs.extract_name(title, idxbl->path);
813         if( asset || track->data_type == TRACK_AUDIO ) {
814                 char number[BCSTRLEN];
815                 sprintf(number, " #%d", channel + 1);
816                 strcat(title, number);
817         }
818 }
819