1// Copyright 1999, Be Incorporated. All Rights Reserved.
2// Copyright 2000-2004, Jun Suzuki. All Rights Reserved.
3// Copyright 2007, Stephan A��mus. All Rights Reserved.
4// This file may be used under the terms of the Be Sample Code License.
5#include "MediaConverterApp.h"
6
7#include <inttypes.h>
8#include <new>
9#include <stdio.h>
10#include <string.h>
11
12#include <Alert.h>
13#include <Catalog.h>
14#include <fs_attr.h>
15#include <Locale.h>
16#include <MediaFile.h>
17#include <MediaTrack.h>
18#include <NumberFormat.h>
19#include <Mime.h>
20#include <Path.h>
21#include <String.h>
22#include <StringFormat.h>
23#include <View.h>
24
25#include "MediaConverterWindow.h"
26#include "MediaEncoderWindow.h"
27#include "MessageConstants.h"
28
29
30#undef B_TRANSLATION_CONTEXT
31#define B_TRANSLATION_CONTEXT "MediaConverter"
32
33
34const char APP_SIGNATURE[] = "application/x-vnd.Haiku-MediaConverter";
35
36
37MediaConverterApp::MediaConverterApp()
38	:
39	BApplication(APP_SIGNATURE),
40	fWin(NULL),
41	fConvertThreadID(-1),
42	fConverting(false),
43	fCancel(false)
44{
45	// TODO: implement settings for window pos
46	fWin = new MediaConverterWindow(BRect(50, 50, 520, 555));
47}
48
49
50MediaConverterApp::~MediaConverterApp()
51{
52	if (fConvertThreadID >= 0) {
53		fCancel = true;
54		status_t exitValue;
55		wait_for_thread(fConvertThreadID, &exitValue);
56	}
57}
58
59
60// #pragma mark -
61
62
63void
64MediaConverterApp::MessageReceived(BMessage *msg)
65{
66	switch (msg->what) {
67		case FILE_LIST_CHANGE_MESSAGE:
68			if (fWin->Lock()) {
69				bool enable = fWin->CountSourceFiles() > 0;
70				fWin->SetEnabled(enable, enable);
71				fWin->Unlock();
72			}
73			break;
74
75		case START_CONVERSION_MESSAGE:
76			if (!fConverting)
77				StartConverting();
78			break;
79
80		case CANCEL_CONVERSION_MESSAGE:
81			fCancel = true;
82			break;
83
84		case CONVERSION_DONE_MESSAGE:
85			fCancel = false;
86			fConverting = false;
87			DetachCurrentMessage();
88			BMessenger(fWin).SendMessage(msg);
89			break;
90
91		default:
92			BApplication::MessageReceived(msg);
93			break;
94	}
95}
96
97
98void
99MediaConverterApp::ReadyToRun()
100{
101	fWin->Show();
102	fWin->PostMessage(INIT_FORMAT_MENUS);
103}
104
105
106void
107MediaConverterApp::RefsReceived(BMessage* msg)
108{
109	entry_ref ref;
110	int32 i = 0;
111	BString errorFiles;
112	int32 errors = 0;
113
114	// from Open dialog or drag & drop
115
116	while (msg->FindRef("refs", i++, &ref) == B_OK) {
117
118		uint32 flags = 0; // B_MEDIA_FILE_NO_READ_AHEAD
119		BMediaFile* file = new(std::nothrow) BMediaFile(&ref, flags);
120
121		if (file == NULL || file->InitCheck() != B_OK) {
122			errorFiles << ref.name << "\n";
123			errors++;
124			delete file;
125			continue;
126		}
127		if (fWin->Lock()) {
128			if (!fWin->AddSourceFile(file, ref))
129				delete file;
130			fWin->Unlock();
131		}
132	}
133
134	if (errors) {
135		BString alertText;
136		static BStringFormat format(B_TRANSLATE("{0, plural, "
137			"one{The file was not recognized as a supported media file:} "
138			"other{# files were not recognized as supported media files:}}"));
139		format.Format(alertText, errors);
140
141		alertText << "\n" << errorFiles;
142		BAlert* alert = new BAlert((errors > 1) ?
143			B_TRANSLATE("Error loading files") :
144			B_TRANSLATE("Error loading a file"),
145			alertText.String(),	B_TRANSLATE("Continue"), NULL, NULL,
146			B_WIDTH_AS_USUAL, B_STOP_ALERT);
147		alert->Go();
148	}
149}
150
151
152// #pragma mark -
153
154
155bool
156MediaConverterApp::IsConverting() const
157{
158	return fConverting;
159}
160
161
162void
163MediaConverterApp::StartConverting()
164{
165	bool locked = fWin->Lock();
166
167	if (locked && (fWin->CountSourceFiles() > 0)) {
168		fConvertThreadID = spawn_thread(MediaConverterApp::_RunConvertEntry,
169			"converter thread", B_LOW_PRIORITY, (void *)this);
170		if (fConvertThreadID >= 0) {
171			fConverting = true;
172			fCancel = false;
173			resume_thread(fConvertThreadID);
174		}
175	}
176
177	if (locked) {
178		fWin->Unlock();
179	}
180}
181
182
183void
184MediaConverterApp::SetStatusMessage(const char* message)
185{
186	if (fWin != NULL && fWin->Lock()) {
187		fWin->SetStatusMessage(message);
188		fWin->Unlock();
189	}
190}
191
192
193// #pragma mark -
194
195BEntry
196MediaConverterApp::_CreateOutputFile(BDirectory directory,
197	entry_ref* ref, media_file_format* outputFormat)
198{
199	BString name(ref->name);
200	// create output file name
201	int32 extIndex = name.FindLast('.');
202	if (extIndex != B_ERROR)
203		name.Truncate(extIndex + 1);
204	else
205		name.Append(".");
206
207	name.Append(outputFormat->file_extension);
208
209	BEntry directoryEntry;
210	directory.GetEntry(&directoryEntry);
211	if (!directoryEntry.Exists()) {
212		BAlert* alert = new BAlert(B_TRANSLATE("Error"),
213			B_TRANSLATE("Selected directory not found. "
214				"Defaulting to /boot/home"),
215			B_TRANSLATE("OK"));
216		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
217		alert->Go();
218		directory.SetTo("/boot/home");
219	}
220
221	BEntry inEntry(ref);
222	BEntry outEntry;
223
224	if (inEntry.InitCheck() == B_OK) {
225		// ensure that output name is unique
226		int32 len = name.Length();
227		int32 i = 1;
228		while (directory.Contains(name.String())) {
229			name.Truncate(len);
230			name << " " << i;
231			i++;
232		}
233		outEntry.SetTo(&directory, name.String());
234	}
235
236	return outEntry;
237}
238
239
240int32
241MediaConverterApp::_RunConvertEntry(void* castToMediaConverterApp)
242{
243	MediaConverterApp* app = (MediaConverterApp*)castToMediaConverterApp;
244	app->_RunConvert();
245	return 0;
246}
247
248
249void
250MediaConverterApp::_RunConvert()
251{
252	bigtime_t start = 0;
253	bigtime_t end = 0;
254	int32 audioQuality = 75;
255	int32 videoQuality = 75;
256
257	if (fWin->Lock()) {
258		char *a;
259		start = strtoimax(fWin->StartDuration(), &a, 0) * 1000;
260		end = strtoimax(fWin->EndDuration(), &a, 0) * 1000;
261		audioQuality = fWin->AudioQuality();
262		videoQuality = fWin->VideoQuality();
263		fWin->Unlock();
264	}
265
266	int32 srcIndex = 0;
267
268	BMediaFile *inFile(NULL), *outFile(NULL);
269	BEntry outEntry;
270	entry_ref inRef;
271	entry_ref outRef;
272	BPath path;
273	BString name;
274
275	while (!fCancel) {
276		if (fWin->Lock()) {
277			status_t r = fWin->GetSourceFileAt(srcIndex, &inFile, &inRef);
278			if (r == B_OK) {
279				media_codec_info* audioCodec;
280				media_codec_info* videoCodec;
281				media_file_format* fileFormat;
282				fWin->GetSelectedFormatInfo(&fileFormat, &audioCodec,
283					&videoCodec);
284				BDirectory directory = fWin->OutputDirectory();
285				fWin->Unlock();
286				outEntry = _CreateOutputFile(directory, &inRef, fileFormat);
287
288				// display file name
289
290				outEntry.GetPath(&path);
291				name.SetTo(path.Leaf());
292
293				if (outEntry.InitCheck() == B_OK) {
294					entry_ref outRef;
295					outEntry.GetRef(&outRef);
296					outFile = new BMediaFile(&outRef, fileFormat);
297
298					BString tmp(
299						B_TRANSLATE("Output file '%filename' created"));
300					tmp.ReplaceAll("%filename", name);
301					name = tmp;
302				} else {
303					BString tmp(B_TRANSLATE("Error creating '%filename'"));
304					tmp.ReplaceAll("%filename", name);
305					name = tmp;
306				}
307
308				if (fWin->Lock()) {
309					fWin->SetFileMessage(name.String());
310					fWin->Unlock();
311				}
312
313				if (outFile != NULL) {
314					r = _ConvertFile(inFile, outFile, audioCodec, videoCodec,
315						audioQuality, videoQuality, start, end);
316
317					// set mime
318					update_mime_info(path.Path(), false, false, false);
319
320					fWin->Lock();
321					if (r == B_OK) {
322						fWin->RemoveSourceFile(srcIndex);
323					} else {
324						srcIndex++;
325						BString error(
326							B_TRANSLATE("Error converting '%filename'"));
327						error.ReplaceAll("%filename", inRef.name);
328						fWin->SetStatusMessage(error.String());
329					}
330					fWin->Unlock();
331				}
332			} else {
333				srcIndex++;
334				BString error(
335					B_TRANSLATE("Error converting '%filename'"));
336				error.ReplaceAll("%filename", inRef.name);
337				fWin->SetStatusMessage(error.String());
338				fWin->Unlock();
339				break;
340			}
341		} else {
342			break;
343		}
344	}
345
346	BMessenger(this).SendMessage(CONVERSION_DONE_MESSAGE);
347}
348
349
350// #pragma mark -
351
352
353status_t
354MediaConverterApp::_ConvertFile(BMediaFile* inFile, BMediaFile* outFile,
355	media_codec_info* audioCodec, media_codec_info* videoCodec,
356	int32 audioQuality, int32 videoQuality,
357	bigtime_t startDuration, bigtime_t endDuration)
358{
359	BMediaTrack* inVidTrack = NULL;
360	BMediaTrack* inAudTrack = NULL;
361	BMediaTrack* outVidTrack = NULL;
362	BMediaTrack* outAudTrack = NULL;
363
364	media_format inFormat;
365	media_format outAudFormat;
366	media_format outVidFormat;
367
368	media_raw_audio_format* raf = NULL;
369	media_raw_video_format* rvf = NULL;
370
371	int32 width = -1;
372	int32 height = -1;
373
374	uint8* videoBuffer = NULL;
375	uint8* audioBuffer = NULL;
376
377	// gather the necessary format information and construct output tracks
378	int64 videoFrameCount = 0;
379	int64 audioFrameCount = 0;
380
381	status_t ret = B_OK;
382	bool multiTrack = false;
383
384	BNumberFormat fNumberFormat;
385
386	int32 tracks = inFile->CountTracks();
387	for (int32 i = 0; i < tracks && (!outAudTrack || !outVidTrack); i++) {
388		BMediaTrack* inTrack = inFile->TrackAt(i);
389		inFormat.Clear();
390		inTrack->EncodedFormat(&inFormat);
391		if (inFormat.IsAudio() && (audioCodec != NULL)) {
392			if (outAudTrack != NULL) {
393				multiTrack = true;
394				continue;
395			}
396			inAudTrack = inTrack;
397			outAudFormat.Clear();
398			outAudFormat.type = B_MEDIA_RAW_AUDIO;
399			raf = &(outAudFormat.u.raw_audio);
400			inTrack->DecodedFormat(&outAudFormat);
401
402			audioBuffer = new uint8[raf->buffer_size];
403//			audioFrameSize = (raf->format & media_raw_audio_format::B_AUDIO_SIZE_MASK)
404//			audioFrameSize = (raf->format & 0xf) * raf->channel_count;
405			outAudTrack = outFile->CreateTrack(&outAudFormat, audioCodec);
406
407			// Negociate the format with the inTrack again in case the codec
408			// made some changes to it...
409			inTrack->DecodedFormat(&outAudFormat);
410
411			if (outAudTrack != NULL) {
412				if (outAudTrack->SetQuality(audioQuality / 100.0f) != B_OK
413					&& fWin->Lock()) {
414					fWin->SetAudioQualityLabel(
415						B_TRANSLATE("Audio quality not supported"));
416					fWin->Unlock();
417				}
418			} else {
419				SetStatusMessage(B_TRANSLATE("Error creating track."));
420			}
421
422		} else if (inFormat.IsVideo() && (videoCodec != NULL)) {
423			if (outVidTrack != NULL) {
424				multiTrack = true;
425				continue;
426			}
427			inVidTrack = inTrack;
428			width = (int32)inFormat.Width();
429			height = (int32)inFormat.Height();
430
431			// construct desired decoded video format
432			outVidFormat.Clear();
433			outVidFormat.type = B_MEDIA_RAW_VIDEO;
434			rvf = &(outVidFormat.u.raw_video);
435			rvf->last_active = (uint32)(height - 1);
436			rvf->orientation = B_VIDEO_TOP_LEFT_RIGHT;
437			rvf->display.format = B_RGB32;
438			rvf->display.bytes_per_row = 4 * width;
439			rvf->display.line_width = width;
440			rvf->display.line_count = height;
441
442			inVidTrack->DecodedFormat(&outVidFormat);
443
444			if (rvf->display.format == B_RGBA32) {
445				printf("fixing color space (B_RGBA32 -> B_RGB32)");
446				rvf->display.format = B_RGB32;
447			}
448			// Transfer the display aspect ratio.
449			if (inFormat.type == B_MEDIA_ENCODED_VIDEO) {
450				rvf->pixel_width_aspect
451					= inFormat.u.encoded_video.output.pixel_width_aspect;
452				rvf->pixel_height_aspect
453					= inFormat.u.encoded_video.output.pixel_height_aspect;
454			} else {
455				rvf->pixel_width_aspect
456					= inFormat.u.raw_video.pixel_width_aspect;
457				rvf->pixel_height_aspect
458					= inFormat.u.raw_video.pixel_height_aspect;
459			}
460
461			videoBuffer = new (std::nothrow) uint8[height
462				* rvf->display.bytes_per_row];
463			outVidTrack = outFile->CreateTrack(&outVidFormat, videoCodec);
464
465			if (outVidTrack != NULL) {
466				// DLM Added to use 3ivx Parameter View
467				const char* videoQualitySupport = NULL;
468				BView* encoderView = outVidTrack->GetParameterView();
469				if (encoderView) {
470					MediaEncoderWindow* encoderWin
471						= new MediaEncoderWindow(BRect(50, 50, 520, 555),
472							encoderView);
473					encoderWin->Go();
474						// blocks until the window is quit
475
476					// The quality setting is ignored by the 3ivx encoder if the
477					// view was displayed, but this method is the trigger to
478					// read all the parameter settings
479					outVidTrack->SetQuality(videoQuality / 100.0f);
480
481					// We can now delete the encoderView created for us by the
482					// encoder
483					delete encoderView;
484					encoderView = NULL;
485
486					videoQualitySupport
487						= B_TRANSLATE("Video using parameters form settings");
488				} else if (outVidTrack->SetQuality(videoQuality / 100.0f)
489					>= B_OK) {
490					videoQualitySupport
491						= B_TRANSLATE("Video quality not supported");
492				}
493
494				if (videoQualitySupport && fWin->Lock()) {
495					fWin->SetVideoQualityLabel(videoQualitySupport);
496					fWin->Unlock();
497				}
498			} else {
499				SetStatusMessage(B_TRANSLATE("Error creating video."));
500			}
501		} else {
502			//  didn't do anything with the track
503			SetStatusMessage(
504				B_TRANSLATE("Input file not recognized as Audio or Video"));
505			inFile->ReleaseTrack(inTrack);
506		}
507	}
508
509	if (!outVidTrack && !outAudTrack) {
510		printf("MediaConverterApp::_ConvertFile() - no tracks found!\n");
511		ret = B_ERROR;
512	}
513
514	if (multiTrack) {
515		BAlert* alert = new BAlert(B_TRANSLATE("Multi-track file detected"),
516		B_TRANSLATE("The file has multiple audio or video tracks, only the first one of each will "
517			"be converted."),
518		B_TRANSLATE("Understood"), NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
519		alert->Go();
520	}
521
522	if (fCancel) {
523		// don't have any video or audio tracks here, or cancelled
524		printf("MediaConverterApp::_ConvertFile()"
525				" - user canceled before transcoding\n");
526		ret = B_CANCELED;
527	}
528
529	if (ret < B_OK) {
530		delete[] audioBuffer;
531		delete[] videoBuffer;
532		delete outFile;
533		return ret;
534	}
535
536	outFile->CommitHeader();
537	// this is where you would call outFile->AddCopyright(...)
538
539	int64 framesRead;
540	media_header mh;
541	int32 lastPercent, currPercent;
542	float completePercent;
543	BString status;
544
545	int64 start;
546	int64 end;
547	int32 stat = 0;
548
549	// read video from source and write to destination, if necessary
550	if (outVidTrack != NULL) {
551		lastPercent = -1;
552		videoFrameCount = inVidTrack->CountFrames();
553		if (endDuration == 0 || endDuration < startDuration) {
554			start = 0;
555			end = videoFrameCount;
556		} else {
557			inVidTrack->SeekToTime(&endDuration, stat);
558			end = inVidTrack->CurrentFrame();
559			inVidTrack->SeekToTime(&startDuration, stat);
560			start = inVidTrack->CurrentFrame();
561			if (end > videoFrameCount)
562				end =  videoFrameCount;
563			if (start > end)
564				start = 0;
565		}
566
567		framesRead = 0;
568		for (int64 i = start; (i < end) && !fCancel; i += framesRead) {
569			if ((ret = inVidTrack->ReadFrames(videoBuffer, &framesRead,
570					&mh)) != B_OK) {
571				fprintf(stderr, "Error reading video frame %" B_PRId64 ": %s\n", i, strerror(ret));
572				status.SetToFormat(B_TRANSLATE("Error read video frame %" B_PRId64), i);
573				SetStatusMessage(status.String());
574
575				break;
576			}
577
578			if ((ret = outVidTrack->WriteFrames(videoBuffer, framesRead,
579					mh.u.encoded_video.field_flags)) != B_OK) {
580				fprintf(stderr, "Error writing video frame %" B_PRId64 ": %s\n", i, strerror(ret));
581				status.SetToFormat(B_TRANSLATE("Error writing video frame %" B_PRId64), i);
582				SetStatusMessage(status.String());
583
584				break;
585			}
586			completePercent = (float)(i - start) / (float)(end - start) * 100;
587			currPercent = (int32)completePercent;
588			if (currPercent > lastPercent) {
589				lastPercent = currPercent;
590				BString data;
591				double percentValue = (double)currPercent;
592
593				if (fNumberFormat.FormatPercent(data, percentValue / 100) != B_OK) {
594					data.SetToFormat("%" B_PRId32 "%%", currPercent);
595				}
596
597				status.SetToFormat(B_TRANSLATE("Writing video track: %s complete"), data.String());
598				SetStatusMessage(status.String());
599			}
600		}
601		outVidTrack->Flush();
602		inFile->ReleaseTrack(inVidTrack);
603	}
604
605	// read audio from source and write to destination, if necessary
606	if (outAudTrack != NULL) {
607		lastPercent = -1;
608
609		audioFrameCount =  inAudTrack->CountFrames();
610
611		if (endDuration == 0 || endDuration < startDuration) {
612			start = 0;
613			end = audioFrameCount;
614		} else {
615			inAudTrack->SeekToTime(&endDuration, stat);
616			end = inAudTrack->CurrentFrame();
617			inAudTrack->SeekToTime(&startDuration, stat);
618			start = inAudTrack->CurrentFrame();
619			if (end > audioFrameCount)
620				end = audioFrameCount;
621			if (start > end)
622				start = 0;
623		}
624
625		for (int64 i = start; (i < end) && !fCancel; i += framesRead) {
626			if ((ret = inAudTrack->ReadFrames(audioBuffer, &framesRead,
627				&mh)) != B_OK) {
628				fprintf(stderr, "Error reading audio frames: %s\n", strerror(ret));
629				status.SetToFormat(B_TRANSLATE("Error read audio frame %" B_PRId64), i);
630				SetStatusMessage(status.String());
631
632				break;
633			}
634
635			if ((ret = outAudTrack->WriteFrames(audioBuffer,
636				framesRead)) != B_OK) {
637				fprintf(stderr, "Error writing audio frames: %s\n",	strerror(ret));
638				status.SetToFormat(B_TRANSLATE("Error writing audio frame %" B_PRId64), i);
639				SetStatusMessage(status.String());
640
641				break;
642			}
643			completePercent = (float)(i - start) / (float)(end - start) * 100;
644			currPercent = (int32)completePercent;
645			if (currPercent > lastPercent) {
646				lastPercent = currPercent;
647				BString data;
648				double percentValue = (double)currPercent;
649
650				if (fNumberFormat.FormatPercent(data, percentValue / 100) != B_OK) {
651					data.SetToFormat("%" B_PRId32 "%%", currPercent);
652				}
653
654				status.SetToFormat(B_TRANSLATE("Writing audio track: %s complete"), data.String());
655				SetStatusMessage(status.String());
656			}
657		}
658		outAudTrack->Flush();
659		inFile->ReleaseTrack(inAudTrack);
660
661	}
662
663	outFile->CloseFile();
664	delete outFile;
665
666	delete[] videoBuffer;
667	delete[] audioBuffer;
668
669	return ret;
670}
671
672
673// #pragma mark -
674
675
676int
677main(int, char **)
678{
679	MediaConverterApp app;
680	app.Run();
681
682	return 0;
683}
684