Move things round a bit.
[dcpomatic.git] / src / tools / fixlengths.cc
diff --git a/src/tools/fixlengths.cc b/src/tools/fixlengths.cc
new file mode 100644 (file)
index 0000000..52696cd
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+    Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <stdexcept>
+#include <iostream>
+#include <iomanip>
+#include <getopt.h>
+#include <sndfile.h>
+#include <boost/filesystem.hpp>
+#include "lib/film.h"
+
+using namespace std;
+using namespace boost;
+
+void
+help (string n)
+{
+       cerr << "Syntax: " << n << " [--help] [--chop-audio-start] [--chop-audio-end] --film <film>\n";
+}
+
+void
+sox (vector<string> const & audio_files, string const & process)
+{
+       for (vector<string>::const_iterator i = audio_files.begin(); i != audio_files.end(); ++i) {
+               stringstream cmd;
+               cmd << "sox \"" << *i << "\" -t wav \"" << *i << ".tmp\" " << process;
+               cout << "> " << cmd.str() << "\n";
+               int r = ::system (cmd.str().c_str());
+               if (r == -1 || WEXITSTATUS (r) != 0) {
+                       cerr << "fixlengths: call to sox failed.\n";
+                       exit (EXIT_FAILURE);
+               }
+               filesystem::rename (*i + ".tmp", *i);
+       }
+}
+
+int main (int argc, char* argv[])
+{
+       string film_dir;
+       bool chop_audio_start = false;
+       bool chop_audio_end = false;
+       bool pad_audio_end = false;
+       
+       while (1) {
+               static struct option long_options[] = {
+                       { "help", no_argument, 0, 'h' },
+                       { "chop-audio-start", no_argument, 0, 'c' },
+                       { "chop-audio-end", no_argument, 0, 'd' },
+                       { "pad-audio-end", no_argument, 0, 'p' },
+                       { "film", required_argument, 0, 'f' },
+                       { 0, 0, 0, 0 }
+               };
+
+               int option_index = 0;
+               int c = getopt_long (argc, argv, "hcf:", long_options, &option_index);
+
+               if (c == -1) {
+                       break;
+               }
+
+               switch (c) {
+               case 'h':
+                       help (argv[0]);
+                       exit (EXIT_SUCCESS);
+               case 'c':
+                       chop_audio_start = true;
+                       break;
+               case 'd':
+                       chop_audio_end = true;
+                       break;
+               case 'p':
+                       pad_audio_end = true;
+                       break;
+               case 'f':
+                       film_dir = optarg;
+                       break;
+               }
+       }
+
+       if (film_dir.empty ()) {
+               help (argv[0]);
+               exit (EXIT_FAILURE);
+       }
+
+       dvdomatic_setup ();
+
+       Film* film = 0;
+       try {
+               film = new Film (film_dir, true);
+       } catch (std::exception& e) {
+               cerr << argv[0] << ": error reading film `" << film_dir << "' (" << e.what() << ")\n";
+               exit (EXIT_FAILURE);
+       }
+
+       /* XXX: hack */
+       int video_frames = 0;
+       for (filesystem::directory_iterator i = filesystem::directory_iterator (film->j2k_dir()); i != filesystem::directory_iterator(); ++i) {
+               ++video_frames;
+       }
+
+       float const video_length = video_frames / film->frames_per_second();
+       cout << "Video length: " << video_length << " (" << video_frames << " frames at " << film->frames_per_second() << " frames per second).\n";
+
+       vector<string> audio_files = film->audio_files ();
+       if (audio_files.empty ()) {
+               cerr << argv[0] << ": film has no audio files.\n";
+               exit (EXIT_FAILURE);
+       }
+
+       sf_count_t audio_frames = 0;
+       int audio_sample_rate = 0;
+
+       for (vector<string>::iterator i = audio_files.begin(); i != audio_files.end(); ++i) {
+               SF_INFO info;
+               info.format = 0;
+               SNDFILE* sf = sf_open (i->c_str(), SFM_READ, &info);
+               if (sf == 0) {
+                       cerr << argv[0] << ": could not open WAV file for reading.\n";
+                       exit (EXIT_FAILURE);
+               }
+
+               if (audio_frames == 0) {
+                       audio_frames = info.frames;
+               }
+
+               if (audio_sample_rate == 0) {
+                       audio_sample_rate = info.samplerate;
+               }
+
+               if (audio_frames != info.frames) {
+                       cerr << argv[0] << ": audio files have differing lengths.\n";
+                       exit (EXIT_FAILURE);
+               }
+
+               if (audio_sample_rate != info.samplerate) {
+                       cerr << argv[0] << ": audio files have differing sample rates.\n";
+                       exit (EXIT_FAILURE);
+               }
+
+               sf_close (sf);
+       }
+
+       float const audio_length = audio_frames / float (audio_sample_rate);
+
+       cout << "Audio length: " << audio_length << " (" << audio_frames << " frames at " << audio_sample_rate << " frames per second).\n";
+
+       cout << "\n";
+
+       if (audio_length > video_length) {
+               cout << setprecision (3);
+               cout << "Audio " << (audio_length - video_length) << "s longer than video.\n";
+               
+               float const delta = audio_length - video_length;
+               int const delta_samples = delta * audio_sample_rate;
+               
+               if (chop_audio_start) {
+                       cout << "Chopping difference off the start of the audio.\n";
+
+                       stringstream s;
+                       s << "trim " << delta_samples << "s";
+                       sox (audio_files, s.str ());
+                       
+               } else if (chop_audio_end) {
+                       cout << "Chopping difference off the end of the audio.\n";
+
+                       stringstream s;
+                       s << "reverse trim " << delta_samples << "s reverse";
+                       sox (audio_files, s.str ());
+
+               } else {
+                       cout << "Re-run with --chop-audio-start or --chop-audio-end, perhaps.\n";
+               }
+
+       } else if (audio_length < video_length) {
+               cout << setprecision (3);
+               cout << "Audio " << (video_length - audio_length) << "s shorter than video.\n";
+
+               if (pad_audio_end) {
+
+                       float const delta = video_length - audio_length;
+                       int const delta_samples = delta * audio_sample_rate;
+                       stringstream s;
+                       s << "pad 0 " << delta_samples << "s";
+                       sox (audio_files, s.str ());
+               
+               } else {
+                       cout << "Re-run with --pad-audio-end, perhaps.\n";
+               }
+       }
+       
+
+       return 0;
+}