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