#if 0 set -x g++ -W -Wall -s -O2 -o `basename $0 .cc` $0 $* exit $? #endif /* A simple utility to append other vdr recordings to one recording. Copyright © 2003-2005 Jaakko Hyvätti Version 0.3 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. The author may be contacted at: Email: Jaakko.Hyvatti@iki.fi URL: http://www.iki.fi/hyvatti/ Phone: +358 40 5011222 Please send any suggestions and bug reports to the Email address above. 1. Maintain file number counter 2. Find out number of files in first directory 3. Open for writing index.vdr and summary.vdr 4. Loop through the rest of directories 5. Loop through all [0-9][0-9][0-9].vdr in that dir 6. if it is a file, rename the file to first dir 7. if it is a link, recreate the link and remove old one. This leaves links pointing to old directories, but it does not seem to matter for vdr. 8. Read the index file and write modified (by file number) records to first one. 9. append summary.vdr 10. remove old index.vdr, summary.vdr, marks.vdr, resume.vdr and directories The recordings that were appended to another recording and whose files have been moved away have to be manually deleted in vdr recordings menu. */ #include #include #include #include #include #include #include #include #include #include #include using namespace std; struct tIndex { int32_t offset; unsigned char type; unsigned char number; int16_t reserved; }; static bool debug = false; int main (int argc, char **argv) { if (argc < 3) { cerr << "Usage: " << argv[0] << " [ -m ] [ ... ]" << endl << "Appends vdr recordings in source-dir's to destination-dir with -m." << endl << "Without -m creates new recording by linking video files." << endl << "Note that if you remove this new recording, you delete the" << endl << "video files from source recordings." << endl; exit (1); } int c; bool opt_move = false; while (EOF != (c = getopt (argc, argv, "md"))) switch (c) { case 0: break; case 'm': opt_move = true; break; case 'd': debug = true; break; default: cerr << "For help, run without options." << endl; exit (1); } argc -= optind; argv += optind; // 1. Maintain file number counter int filenum = 1; // 2. Find out number of files in first directory (for -m only) string firstdir = argv[0]; dev_t firstdisk; if (opt_move) { for (;;) { assert (filenum < 1000); char f [20]; snprintf (f, 10, "/%03d.vdr", filenum); string fn = firstdir + f; struct stat st; if (0 > lstat (fn.c_str(), &st)) break; // Save the device information so that later we can make sure the user // has given directories only on primary disk, /video0. if (1 == filenum) firstdisk = st.st_dev; else if (firstdisk != st.st_dev) { cerr << "Files are not on same device, oops, i am confused!" << endl; exit (1); } // Save the paths of files on other drives. Not implemented. // Just check for valid links. if (S_ISLNK (st.st_mode)) { char linkdest [1024]; int chars = readlink (fn.c_str(), linkdest, sizeof (linkdest)-1); if (chars < 0) { cerr << fn << ": unable to read symbolic link: " << strerror(errno) << endl; exit (1); } linkdest [chars] = '\0'; if (0 == chars || '/' != linkdest [0]) { cerr << fn << ": Only absolute sumbolic links supported, not \"" << linkdest << "\"" << endl; exit (1); } // This is not implemented. } filenum++; } // The filenum is the number of first file not yet found, and // the first file to be created in the destination directory. if (filenum < 2) { cerr << firstdir << "/001.vdr not found!" << endl; exit (1); } // End of -m section for first dir check. } // 3. Open for writing index.vdr and summary.vdr // Create if no -m, append if -m string indexname = firstdir + "/index.vdr"; ofstream indexf (indexname.c_str(), opt_move ? ios::app : ios::out); assert (indexf); string summaryname = firstdir + "/summary.vdr"; ofstream summaryf (summaryname.c_str(), opt_move ? ios::app : ios::out); assert (summaryf); //string markname = firstdir + "/marks.vdr"; //ofstream markf (markname.c_str(), opt_move ? ios::app : ios::out); //assert (markf); // 4. Loop through the rest of directories for (int argi = 1; argi < argc; ++argi) { // 5. Loop through all [0-9][0-9][0-9].vdr in that dir string thisdir = argv [argi]; int filenum2 = 0; const int filenumoffset = filenum - 1; for (;;) { filenum2 ++; assert (filenum2 < 1000); char f [20]; snprintf (f, 10, "/%03d.vdr", filenum2); string fn2 = thisdir + f; struct stat st; if (0 > lstat (fn2.c_str(), &st)) break; if (1 == filenum) firstdisk = st.st_dev; else if (firstdisk != st.st_dev) { cerr << "Directories " << firstdir << endl << "and " << thisdir << "are not in the same disk." << endl << "Usually they should be under /video0 or /video." << endl; exit (1); } // Destination serial number. snprintf (f, 10, "/%03d.vdr", filenum); string fn = firstdir + f; // Check that the destination does not exist, just in case. struct stat st0; if (0 == lstat (fn.c_str(), &st0)) { cerr << fn << ": file already exists, unable to create" << endl; exit (1); } if (S_ISREG (st.st_mode)) { // 6. if it is a file, rename the file to first dir (-m) // Without -m, symlink. if (opt_move) { if (debug) { cout << "mv " << fn2 << " " << fn << endl; } else if (0 > rename (fn2.c_str(), fn.c_str())) { cerr << "Unable to rename " << fn2 << " to " << fn << ": " << strerror(errno) << endl; exit (1); } } else { if (debug) { cout << "ln -s " << fn2 << " " << fn << endl; } else if (0 > symlink (fn2.c_str(), fn.c_str())) { cerr << "Unable to link " << fn2 << " to " << fn << ": " << strerror(errno) << endl; exit (1); } } } else if (S_ISLNK (st.st_mode)) { // 7. if it is a link, recreate the link and remove old one (-m). // Without -m, do not remove old one. int destsize = 1024 + 2 * fn2.length(); char *linkdest = new char [destsize]; assert (linkdest); int chars = readlink (fn2.c_str(), linkdest, destsize); if (chars < 0) { cerr << fn2 << ": unable to read symbolic link: " << strerror(errno) << endl; exit (1); } if (chars >= destsize) { // Overwrites the last byte, never mind. linkdest [destsize-1] = '\0'; cerr << fn2 << ": too long link, giving up. This is the start of destination: " << linkdest << endl; exit (0); } linkdest [chars] = '\0'; if (0 == chars || '/' != linkdest [0]) { cerr << fn2 << ": Only absolute symbolic links supported, not \"" << linkdest << "\"" << endl; exit (1); } if (debug) { cout << "ln -s " << linkdest << " " << fn << endl; } else if (0 > symlink (linkdest, fn.c_str())) { cerr << "Unable to link " << linkdest << " (from " << fn2 << ") to " << fn << ": " << strerror(errno) << endl; exit (1); } delete [] linkdest; if (opt_move) if (debug) cerr << "rm " << fn2 << endl; else unlink (fn2.c_str()); } else { cerr << fn2 << ": file is not a link and not a file." << endl; exit (1); } ++filenum; } // for each ???.vdr file if (filenum2 < 2) { cerr << thisdir << "/001.vdr: no video file!" << endl; exit (1); } // 8. Read the index file and write modified (by file number) // records to first one. string thisindex = thisdir + "/index.vdr"; ifstream thisi (thisindex.c_str()); if (!thisi) { cerr << thisindex << ": unable to open: " << strerror(errno) << endl; exit (1); } tIndex irec; for (;;) { thisi.read ((char *)&irec, sizeof (irec)); if (0 == thisi.gcount() && thisi.eof()) break; if (sizeof (irec) != thisi.gcount()) { if (!thisi.eof()) { cerr << thisindex << ": read error, read " << thisi.gcount() << " bytes: " << strerror(errno) << endl; exit (1); } cerr << thisindex << ": warning: last record was " << thisi.gcount() << " bytes, should be " << sizeof (irec) << endl; break; } irec.number = irec.number + filenumoffset; if (!indexf.write ((char *)&irec, sizeof (irec))) { cerr << indexname << ": write error: " << strerror(errno) << endl; exit (1); } } thisi.close(); // 9. append summary.vdr if (opt_move || 1 != argi) summaryf << endl << "----------- appended recording:" << endl; string thissummary = thisdir + "/summary.vdr"; ifstream thiss (thissummary.c_str()); if (!thiss) { cerr << thissummary << ": unable to open: " << strerror(errno) << endl; exit (1); } char tmpbuf [50]; for (;;) { thiss.read (tmpbuf, sizeof (tmpbuf)); if (0 == thiss.gcount() && thiss.eof()) break; if (0 == thiss.gcount()) { cerr << thissummary << ": read error: " << strerror(errno) << endl; exit (1); } if (!summaryf.write (tmpbuf, thiss.gcount())) { cerr << summaryname << ": write error: " << strerror(errno) << endl; exit (1); } } thiss.close(); // 10. remove old index.vdr, summary.vdr, marks.vdr, resume.vdr and // directories // Skip that for now. if (0 && opt_move) if (debug) cerr << "rm " << thisindex << " " << thissummary << endl; else { unlink (thisindex.c_str()); unlink (thissummary.c_str()); } } // for all directories summaryf.close(); indexf.close(); cout << "Done." << endl; return 0; }