3 * Copyright (C) 2015 Adam Williams <broadcast at earthling dot net>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 #include "audiodevice.h"
30 #include "edlsession.h"
32 #include "filesystem.h"
33 #include "formattools.h"
34 #include "indexfile.h"
36 #include "localsession.h"
37 #include "mainerror.h"
38 #include "mainindexes.h"
39 #include "mainprogress.h"
43 #include "mwindowgui.h"
44 #include "packagedispatcher.h"
45 #include "packagerenderer.h"
48 #include "preferences.h"
55 #define HEIGHT yS(320)
58 ConvertRender::ConvertRender(MWindow *mwindow)
61 this->mwindow = mwindow;
65 progress_timer = new Timer;
67 counter_lock = new Mutex("ConvertDialog::counter_lock");
70 failed = 0; canceled = 0;
75 ConvertRender::~ConvertRender()
80 delete progress_timer;
81 delete convert_progress;
83 format_asset->remove_user();
87 void ConvertRender::reset()
89 for( int i=0,n=orig_idxbls.size(); i<n; ++i )
90 orig_idxbls[i]->remove_user();
91 orig_idxbls.remove_all();
92 for( int i=0,n=orig_copies.size(); i<n; ++i )
93 orig_copies[i]->remove_user();
94 orig_copies.remove_all();
95 for( int i=0,n=needed_idxbls.size(); i<n; ++i )
96 needed_idxbls[i]->remove_user();
97 needed_idxbls.remove_all();
98 for( int i=0,n=needed_copies.size(); i<n; ++i )
99 needed_copies[i]->remove_user();
100 needed_copies.remove_all();
103 void ConvertRender::to_convert_path(char *new_path, Indexable *idxbl)
105 strcpy(new_path, idxbl->path);
106 int n = strlen(suffix);
107 char *ep = new_path + strlen(new_path);
108 char *sfx = strrchr(new_path, '.');
110 // insert suffix, path.sfx => path+suffix-sfx.ext
111 char *bp = ep, *cp = (ep += n);
112 while( --bp > sfx ) *--cp = *bp;
116 // insert suffix, path => path+suffix.ext
119 for( const char *cp=suffix; --n>=0; ++cp ) *sfx++ = *cp;
121 const char *ext = format_asset->format == FILE_FFMPEG ?
122 format_asset->fformat :
123 File::get_tag(format_asset->format);
124 while( *ext ) *ep++ = *ext++;
128 int ConvertRender::from_convert_path(char *path, Indexable *idxbl)
130 strcpy(path, idxbl->path);
131 char *ep = path + strlen(path);
132 const char *ext = format_asset->format == FILE_FFMPEG ?
133 format_asset->fformat :
134 File::get_tag(format_asset->format);
135 const char *rp = ext + strlen(ext);
138 } while( rp>=ext && ep>=path && *ep == *rp );
139 if( rp >= ext || ep < path && *--ep != '.' ) return 1; // didnt find .ext
140 int n = strlen(suffix), len = 0;
141 char *lp = ep - n; // <suffix-chars>+.ext
142 for( ; --lp>=path ; ++len ) {
143 if( strncmp(lp, suffix, n) ) continue;
144 if( !len || lp[n] == '-' ) break;
146 if( lp < path ) return 1; // didnt find suffix
147 if( *(rp=lp+n) == '-' ) {
148 // remove suffix, path+suffix-sfx.ext >= path.sfx
150 while( rp < ep ) *lp++ = *rp++;
152 // remove suffix, path+suffix.ext => path
157 double ConvertRender::get_video_length(Indexable *idxbl)
159 int64_t video_frames = idxbl->get_video_frames();
160 double frame_rate = idxbl->get_frame_rate();
161 if( video_frames < 0 && mwindow->edl->session->si_useduration )
162 video_frames = mwindow->edl->session->si_duration * frame_rate;
163 if( video_frames < 0 ) video_frames = 1;
164 return !video_frames ? 0 : video_frames / frame_rate;
167 double ConvertRender::get_audio_length(Indexable *idxbl)
169 int64_t audio_samples = idxbl->get_audio_samples();
170 return !audio_samples ? 0 :
171 (double)audio_samples / idxbl->get_sample_rate();
174 double ConvertRender::get_length(Indexable *idxbl)
176 return bmax(get_video_length(idxbl), get_audio_length(idxbl));
179 int ConvertRender::match_format(Asset *asset)
182 return format_asset->audio_data == asset->audio_data &&
183 format_asset->video_data == asset->video_data ? 1 : 0;
186 EDL *ConvertRender::convert_edl(EDL *edl, Indexable *idxbl)
188 Asset *copy_asset = edl->assets->get_asset(idxbl->path);
189 if( !copy_asset ) return 0;
190 if( !copy_asset->layers && !copy_asset->channels ) return 0;
191 // make a clip from 1st video track and audio tracks
192 EDL *copy_edl = new EDL(edl);
193 copy_edl->create_objects();
194 copy_edl->set_path(copy_asset->path);
195 char path[BCTEXTLEN];
196 FileSystem fs; fs.extract_name(path, copy_asset->path);
197 strcpy(copy_edl->local_session->clip_title, path);
198 strcpy(copy_edl->local_session->clip_notes, _("Transcode clip"));
200 double video_length = get_video_length(idxbl);
201 double audio_length = get_audio_length(idxbl);
202 copy_edl->session->video_tracks =
203 video_length > 0 ? 1 : 0;
204 copy_edl->session->audio_tracks =
205 audio_length > 0 ? copy_asset->channels : 0;
207 copy_edl->create_default_tracks();
208 Track *current = copy_edl->session->video_tracks ?
209 copy_edl->tracks->first : 0;
210 for( int vtrack=0; current; current=NEXT ) {
211 if( current->data_type != TRACK_VIDEO ) continue;
212 current->insert_asset(copy_asset, 0, video_length, 0, vtrack);
215 current = copy_edl->session->audio_tracks ?
216 copy_edl->tracks->first : 0;
217 for( int atrack=0; current; current=NEXT ) {
218 if( current->data_type != TRACK_AUDIO ) continue;
219 current->insert_asset(copy_asset, 0, audio_length, 0, atrack);
220 Autos *pan_autos = current->automation->autos[AUTOMATION_PAN];
221 PanAuto *pan_auto = (PanAuto*)pan_autos->get_auto_for_editing(-1);
222 for( int i=0; i < MAXCHANNELS; ++i ) pan_auto->values[i] = 0;
223 pan_auto->values[atrack++] = 1;
224 if( atrack >= MAXCHANNELS ) atrack = 0;
226 copy_edl->folder_no = AW_MEDIA_FOLDER;
230 int ConvertRender::add_original(EDL *edl, Indexable *idxbl)
232 char new_path[BCTEXTLEN];
233 // if idxbl already a convert
234 if( !from_convert_path(new_path, idxbl) ) return 0;
235 // don't convert if not readable
236 if( idxbl->is_asset && access(idxbl->path, R_OK) ) return 0;
237 // found readable unconverted asset
238 to_convert_path(new_path, idxbl);
239 // add to orig_idxbls & orig_copies if it isn't already there.
241 for( int i = 0; !got_it && i<orig_copies.size(); ++i )
242 got_it = !strcmp(orig_copies[i]->path, new_path);
243 if( got_it ) return 0;
245 orig_idxbls.append(idxbl);
247 Asset *convert = edl->assets->get_asset(new_path);
249 convert = new Asset(new_path);
251 if( fs.get_size(new_path) > 0 ) {
252 // copy already on disk
253 int64_t orig_mtime = fs.get_date(idxbl->path);
254 int64_t convert_mtime = fs.get_date(new_path);
255 // render needed if it is too old
256 if( orig_mtime < convert_mtime ) {
258 int ret = file.open_file(mwindow->preferences, convert, 1, 0);
259 // render not needed if can use copy
260 if( ret == FILE_OK ) {
261 if( match_format(file.asset) ) {
262 mwindow->mainindexes->add_indexable(convert);
263 mwindow->mainindexes->start_build();
272 else if( match_format(convert) ) {
273 // dont render if copy already an assets
280 eprintf(_("transcode target file exists but is incorrect format:\n%s\n"
281 "remove file from disk before transcode to new format.\n"), new_path);
284 orig_copies.append(convert);
286 convert->copy_format(format_asset, 0);
287 // new compression parameters
288 convert->video_data = format_asset->video_data;
289 if( convert->video_data ) {
291 convert->width = idxbl->get_w();
292 if( convert->width & 1 ) ++convert->width;
293 convert->actual_width = convert->width;
294 convert->height = idxbl->get_h();
295 if( convert->height & 1 ) ++convert->height;
296 convert->actual_height = convert->height;
297 convert->frame_rate = mwindow->edl->session->frame_rate;
299 convert->audio_data = format_asset->audio_data;
300 if( convert->audio_data ) {
301 convert->sample_rate = mwindow->edl->session->sample_rate;
303 convert->folder_no = AW_MEDIA_FOLDER;
304 add_needed(idxbl, convert);
309 void ConvertRender::add_needed(Indexable *idxbl, Asset *convert)
311 needed_idxbls.append(idxbl);
313 needed_copies.append(convert);
318 int ConvertRender::is_canceled()
320 return progress->is_cancelled();
323 int ConvertRender::find_convertable_assets(EDL *edl)
326 Asset *orig_asset = edl->assets->first;
328 for( ; orig_asset; orig_asset=orig_asset->next ) {
329 int ret = add_original(edl, orig_asset);
330 if( ret < 0 ) return -1;
336 void ConvertRender::set_format(Asset *asset, const char *suffix)
338 delete [] this->suffix;
339 this->suffix = cstrdup(suffix);
341 format_asset = new Asset();
342 format_asset->copy_from(asset, 0);
345 void ConvertRender::start_convert(float beep, int remove_originals)
348 this->remove_originals = remove_originals;
352 void ConvertRender::run()
354 mwindow->stop_brender();
357 failed = 0; canceled = 0;
359 progress_timer->update();
362 for( int i=0; !failed && !canceled && i<needed_copies.size(); ++i )
365 canceled = progress->is_cancelled();
366 printf(_("convert: failed=%d canceled=%d\n"), failed, canceled);
367 double elapsed_time = progress_timer->get_scaled_difference(1);
369 char elapsed[BCSTRLEN], text[BCSTRLEN];
370 Units::totext(elapsed, elapsed_time, TIME_HMS2);
371 printf(_("TranscodeRender::run: done in %s\n"), elapsed);
373 strcpy(text, _("transcode cancelled"));
375 strcpy(text, _("transcode failed"));
377 sprintf(text, _("transcode %d files, render time %s"),
378 needed_copies.size(), elapsed);
383 mwindow->finish_convert(remove_originals);
385 else if( !canceled ) {
386 eprintf(_("Error making transcode."));
389 if( !canceled && beep > 0 ) {
391 mwindow->beep(4000., 0.5, beep);
393 mwindow->beep(1000., 0.5, beep);
395 mwindow->beep(4000., 0.5, beep);
398 mwindow->beep(2000., 2.0, beep);
400 mwindow->restart_brender();
403 void ConvertRender::start_progress()
406 for( int i = 0; i < needed_idxbls.size(); i++ ) {
407 Indexable *orig_idxbl = needed_idxbls[i];
408 double length = get_length(orig_idxbl);
411 int64_t total_samples = total_len * format_asset->sample_rate;
412 mwindow->gui->lock_window("Render::start_progress");
413 progress = mwindow->mainprogress->
414 start_progress(_("Transcode files..."), total_samples);
415 mwindow->gui->unlock_window();
416 convert_progress = new ConvertProgress(mwindow, this);
417 convert_progress->start();
420 void ConvertRender::stop_progress(const char *msg)
422 delete convert_progress; convert_progress = 0;
423 mwindow->gui->lock_window("ConvertRender::stop_progress");
425 mwindow->mainprogress->end_progress(progress);
427 mwindow->gui->show_message(msg);
428 mwindow->gui->update_default_message();
429 mwindow->gui->unlock_window();
433 ConvertPackageRenderer::ConvertPackageRenderer(ConvertRender *render)
436 this->render = render;
439 ConvertPackageRenderer::~ConvertPackageRenderer()
443 int ConvertPackageRenderer::get_master()
448 int ConvertPackageRenderer::get_result()
450 return render->result;
453 void ConvertPackageRenderer::set_result(int value)
456 render->result = value;
459 void ConvertPackageRenderer::set_progress(int64_t value)
461 render->counter_lock->lock("ConvertPackageRenderer::set_progress");
462 // Increase total rendered for all nodes
463 render->total_rendered += value;
464 render->counter_lock->unlock();
467 int ConvertPackageRenderer::progress_cancelled()
469 return render->progress && render->progress->is_cancelled();
472 ConvertProgress::ConvertProgress(MWindow *mwindow, ConvertRender *render)
475 this->mwindow = mwindow;
476 this->render = render;
480 ConvertProgress::~ConvertProgress()
486 void ConvertProgress::run()
488 Thread::disable_cancel();
490 if( render->total_rendered != last_value ) {
491 render->progress->update(render->total_rendered);
492 last_value = render->total_rendered;
495 Thread::enable_cancel();
497 Thread::disable_cancel();
501 void ConvertRender::create_copy(int i)
503 Indexable *orig_idxbl = needed_idxbls[i];
504 Asset *needed_copy = needed_copies[i];
505 EDL *edl = convert_edl(mwindow->edl, orig_idxbl);
506 double length = get_length(orig_idxbl);
507 ConvertPackageRenderer renderer(this);
508 renderer.initialize(mwindow, edl, mwindow->preferences, needed_copy);
509 PackageDispatcher dispatcher;
510 dispatcher.create_packages(mwindow, edl, mwindow->preferences,
511 SINGLE_PASS, needed_copy, 0, length, 0);
512 RenderPackage *package = dispatcher.get_package(0);
513 if( !renderer.render_package(package) ) {
514 Asset *asset = mwindow->edl->assets->update(needed_copy);
515 mwindow->mainindexes->add_indexable(asset);
516 mwindow->mainindexes->start_build();
523 ConvertWindow::ConvertWindow(MWindow *mwindow, ConvertDialog *dialog, int x, int y)
524 : BC_Window(_(PROGRAM_NAME ": Transcode settings"), x, y, WIDTH, HEIGHT,
527 this->mwindow = mwindow;
528 this->dialog = dialog;
532 ConvertWindow::~ConvertWindow()
534 lock_window("ConvertWindow::~ConvertWindow");
540 void ConvertWindow::create_objects()
542 lock_window("ConvertWindow::create_objects");
543 int margin = mwindow->theme->widget_border;
544 int lmargin = margin + xS(10);
547 int y = margin + yS(10);
550 add_subwindow(text = new BC_Title(x, y,
551 _("Render untagged assets and replace in project")));
552 y += text->get_h() + margin + yS(10);
554 y += BC_Title::calculate_h(this, _("Tag suffix:")) + margin + yS(10);
557 format_tools = new ConvertFormatTools(mwindow, this, dialog->asset);
558 format_tools->create_objects(x, y, 1, 1, 1, 1, 0, 1, 0, 1, // skip the path
562 add_subwindow(text = new BC_Title(x, y1, _("Tag suffix:")));
563 x = format_tools->format_text->get_x();
564 add_subwindow(suffix_text = new ConvertSuffixText(this, dialog, x, y1));
566 y += margin + yS(10);
568 add_subwindow(remove_originals = new ConvertRemoveOriginals(this, x, y));
570 y += remove_originals->get_h() + margin;
572 add_subwindow(beep_on_done = new ConvertBeepOnDone(this, x, y));
573 x += beep_on_done->get_w() + margin + xS(10);
574 add_subwindow(new BC_Title(x, y+yS(10), _("Beep on done volume")));
575 // y += beep_on_done->get_h() + margin;
577 add_subwindow(new BC_OKButton(this));
578 add_subwindow(new BC_CancelButton(this));
583 ConvertSuffixText::ConvertSuffixText(ConvertWindow *gui,
584 ConvertDialog *dialog, int x, int y)
585 : BC_TextBox(x, y, 160, 1, dialog->suffix)
588 this->dialog = dialog;
591 ConvertSuffixText::~ConvertSuffixText()
595 int ConvertSuffixText::handle_event()
597 strcpy(dialog->suffix, get_text());
601 ConvertFormatTools::ConvertFormatTools(MWindow *mwindow, ConvertWindow *gui, Asset *asset)
602 : FormatTools(mwindow, gui, asset)
607 void ConvertFormatTools::update_format()
609 asset->save_defaults(mwindow->defaults, "CONVERT_", 1, 1, 0, 0, 0);
610 FormatTools::update_format();
614 ConvertMenuItem::ConvertMenuItem(MWindow *mwindow)
615 : BC_MenuItem(_("Transcode..."), _("Alt-e"), 'e')
617 this->mwindow = mwindow;
621 ConvertMenuItem::~ConvertMenuItem()
626 void ConvertMenuItem::create_objects()
628 dialog = new ConvertDialog(mwindow);
631 int ConvertMenuItem::handle_event()
633 mwindow->gui->unlock_window();
635 mwindow->gui->lock_window("ConvertMenuItem::handle_event");
640 ConvertDialog::ConvertDialog(MWindow *mwindow)
642 this->mwindow = mwindow;
645 strcpy(suffix, ".transcode");
646 // quicker than some, not as good as others
647 asset->format = FILE_FFMPEG;
648 strcpy(asset->fformat, "mp4");
649 strcpy(asset->vcodec, "h264.mp4");
650 asset->ff_video_bitrate = 2560000;
651 asset->video_data = 1;
652 strcpy(asset->acodec, "h264.mp4");
653 asset->ff_audio_bitrate = 256000;
654 asset->audio_data = 1;
655 remove_originals = 1;
659 ConvertDialog::~ConvertDialog()
662 asset->remove_user();
665 BC_Window* ConvertDialog::new_gui()
667 asset->format = FILE_FFMPEG;
668 asset->frame_rate = mwindow->edl->session->frame_rate;
669 asset->sample_rate = mwindow->edl->session->sample_rate;
670 asset->load_defaults(mwindow->defaults, "CONVERT_", 1, 1, 0, 1, 0);
671 remove_originals = mwindow->defaults->get("CONVERT_REMOVE_ORIGINALS", remove_originals);
672 beep = mwindow->defaults->get("CONVERT_BEEP", beep);
673 mwindow->defaults->get("CONVERT_SUFFIX", suffix);
674 mwindow->gui->lock_window("ConvertDialog::new_gui");
676 mwindow->gui->get_abs_cursor(cx, cy);
677 gui = new ConvertWindow(mwindow, this, cx - WIDTH/2, cy - HEIGHT/2);
678 gui->create_objects();
679 mwindow->gui->unlock_window();
683 void ConvertDialog::handle_close_event(int result)
686 mwindow->defaults->update("CONVERT_SUFFIX", suffix);
687 mwindow->defaults->update("CONVERT_REMOVE_ORIGINALS", remove_originals);
688 mwindow->defaults->update("CONVERT_BEEP", beep);
689 asset->save_defaults(mwindow->defaults, "CONVERT_", 1, 1, 0, 1, 0);
690 mwindow->start_convert(asset, suffix, beep, remove_originals);
694 ConvertRemoveOriginals::ConvertRemoveOriginals(ConvertWindow *gui, int x, int y)
695 : BC_CheckBox(x, y, gui->dialog->remove_originals, _("Remove originals from project"))
700 ConvertRemoveOriginals::~ConvertRemoveOriginals()
704 int ConvertRemoveOriginals::handle_event()
706 gui->dialog->remove_originals = get_value();
710 ConvertBeepOnDone::ConvertBeepOnDone(ConvertWindow *gui, int x, int y)
711 : BC_FPot(x, y, gui->dialog->beep*100.f, 0.f, 100.f)
716 int ConvertBeepOnDone::handle_event()
718 gui->dialog->beep = get_value()/100.f;