summaryrefslogtreecommitdiffstats
path: root/tdefile-plugins/mpeg/tdefile_mpeg.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tdefile-plugins/mpeg/tdefile_mpeg.cpp')
-rw-r--r--tdefile-plugins/mpeg/tdefile_mpeg.cpp582
1 files changed, 582 insertions, 0 deletions
diff --git a/tdefile-plugins/mpeg/tdefile_mpeg.cpp b/tdefile-plugins/mpeg/tdefile_mpeg.cpp
new file mode 100644
index 00000000..fd015422
--- /dev/null
+++ b/tdefile-plugins/mpeg/tdefile_mpeg.cpp
@@ -0,0 +1,582 @@
+/* This file is part of the KDE project
+ * Copyright (C) 2005 Allan Sandfeld Jensen <kde@carewolf.com>
+ *
+ * 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 version 2.
+ *
+ * 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; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ */
+
+// MPEG KFile plugin
+// Based on reading sourcecode of mpeglib, xinelib and libmpeg3
+// and studying MPEG dumps.
+
+#include <config.h>
+#include "tdefile_mpeg.h"
+
+#include <kprocess.h>
+#include <klocale.h>
+#include <kgenericfactory.h>
+#include <kstringvalidator.h>
+#include <kdebug.h>
+
+#include <tqdict.h>
+#include <tqvalidator.h>
+#include <tqcstring.h>
+#include <tqfile.h>
+#include <tqdatetime.h>
+
+// #include <iostream>
+
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+
+typedef KGenericFactory<KMpegPlugin> MpegFactory;
+
+K_EXPORT_COMPONENT_FACTORY(tdefile_mpeg, MpegFactory( "tdefile_mpeg" ))
+
+KMpegPlugin::KMpegPlugin(TQObject *parent, const char *name,
+ const TQStringList &args)
+
+ : KFilePlugin(parent, name, args)
+{
+ KFileMimeTypeInfo* info = addMimeTypeInfo( "video/mpeg" );
+
+ KFileMimeTypeInfo::GroupInfo* group = 0L;
+
+ group = addGroupInfo(info, "Technical", i18n("Technical Details"));
+
+ KFileMimeTypeInfo::ItemInfo* item;
+
+ item = addItemInfo(group, "Length", i18n("Length"), TQVariant::Int);
+ setUnit(item, KFileMimeTypeInfo::Seconds);
+
+ item = addItemInfo(group, "Resolution", i18n("Resolution"), TQVariant::Size);
+
+ item = addItemInfo(group, "Frame rate", i18n("Frame Rate"), TQVariant::Double);
+ setSuffix(item, i18n("fps"));
+
+ item = addItemInfo(group, "Video codec", i18n("Video Codec"), TQVariant::String);
+ item = addItemInfo(group, "Audio codec", i18n("Audio Codec"), TQVariant::String);
+
+ item = addItemInfo(group, "Aspect ratio", i18n("Aspect ratio"), TQVariant::String);
+}
+
+// Frame-rate table from libmpeg3
+float frame_rate_table[16] =
+{
+ 0.0, /* Pad */
+ (float)24000.0/1001.0, /* Official frame rates */
+ (float)24.0,
+ (float)25.0,
+ (float)30000.0/1001.0,
+ (float)30.0,
+ (float)50.0,
+ (float)60000.0/1001.0,
+ (float)60.0,
+
+ 1, /* Unofficial economy rates */
+ 5,
+ 10,
+ 12,
+ 15,
+ 0,
+ 0,
+};
+
+/* Bitrate indexes */
+int bitrate_123[3][16] =
+ { {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,},
+ {0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,},
+ {0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,} };
+
+static const uint16_t sequence_start = 0x01b3;
+static const uint16_t ext_sequence_start = 0x01b5;
+static const uint16_t gop_start = 0x01b8;
+static const uint16_t audio1_packet = 0x01c0;
+static const uint16_t audio2_packet = 0x01d0;
+static const uint16_t private1_packet = 0x01bd;
+static const uint16_t private2_packet = 0x01bf;
+
+int KMpegPlugin::parse_seq() {
+ uint32_t buf;
+ dstream >> buf;
+
+ horizontal_size = (buf >> 20);
+ vertical_size = (buf >> 8) & ((1<<12)-1);
+ aspect_ratio = (buf >> 4) & ((1<<4)-1);
+ int framerate_code = buf & ((1<<4)-1);
+ frame_rate = frame_rate_table[framerate_code];
+
+ dstream >> buf;
+
+ bitrate = (buf >> 14);
+// kdDebug(7034) << "bitrate: " << bitrate << endl;
+ bool has_intra_matrix = buf & 2;
+ bool has_non_intra_matrix = buf & 1;
+
+ int matrix = 0;
+ if (has_intra_matrix) matrix +=64;
+ if (has_non_intra_matrix) matrix +=64;
+
+ mpeg = 1;
+ return matrix;
+}
+
+void KMpegPlugin::parse_seq_ext() {
+ uint32_t buf;
+ dstream >> buf;
+
+ uint8_t type = buf >> 28;
+ if (type == 1)
+ mpeg = 2;
+
+ /*
+ else
+ if (type == 2) {
+ dstream >> buf;
+ // These are display-sizes. I let them override physical sizes.
+ horizontal_size = (buf >> 18);
+ vertical_size = (buf >> 1) & ((1<<14)-1);
+} */
+}
+
+long KMpegPlugin::parse_gop() {
+ uint32_t buf;
+ dstream >> buf;
+ dstream >> buf;
+
+ int gop_hour = (buf>>26) & ((1<<5)-1);
+ kdDebug(7034) << "gop_hour: " << gop_hour << endl;
+ int gop_minute = (buf>>20) & ((1<<6)-1);
+ kdDebug(7034) << "gop_minute: " << gop_minute << endl;
+ int gop_second = (buf>>13) & ((1<<6)-1);
+ kdDebug(7034) << "gop_second: " << gop_second << endl;
+ int gop_frame = (buf>>7) & ((1<<6)-1);
+ kdDebug(7034) << "gop_frame: " << gop_frame << endl;
+
+ long seconds = gop_hour*60*60 + gop_minute*60 + gop_second;
+ return (long)seconds;
+}
+
+int KMpegPlugin::parse_audio() {
+ uint16_t len;
+ dstream >> len;
+// kdDebug(7034) << "Length of audio packet: " << len << endl;
+
+ uint8_t buf;
+ int i = 0;
+ for(i=0; i<20; i++) {
+ dstream >> buf;
+ if (buf == 0xff) {
+ dstream >> buf;
+ if ((buf & 0xe0) == 0xe0)
+ goto found_sync;
+ }
+ }
+ kdDebug(7034) << "MPEG audio sync not found" << endl;
+ return len-i;
+
+found_sync:
+
+ int layer = ((buf >> 1) & 0x3);
+ if (layer == 1)
+ audio_type = 3;
+ else if (layer == 2)
+ audio_type = 2;
+ else if (layer == 3)
+ audio_type = 1;
+ else
+ kdDebug(7034) << "Invalid MPEG audio layer" << endl;
+
+ dstream >> buf;
+ int bitrate_index = (buf & 0xf0) >> 4;
+ audio_rate = bitrate_123[3-layer][bitrate_index];
+
+ return len-3-i;
+}
+
+int KMpegPlugin::skip_packet() {
+ uint16_t len;
+ dstream >> len;
+// kdDebug(7034) << "Length of skipped packet: " << len << endl;
+
+ return len;
+}
+
+int KMpegPlugin::skip_riff_chunk() {
+ dstream.setByteOrder(TQDataStream::LittleEndian);
+ uint32_t len;
+ dstream >> len;
+// std::cerr << "Length of skipped chunk: " << len << std::endl;
+
+ dstream.setByteOrder(TQDataStream::BigEndian);
+ return len;
+}
+
+int KMpegPlugin::parse_private() {
+ uint16_t len;
+ dstream >> len;
+// kdDebug(7034) << "Length of private packet: " << len << endl;
+
+ // Match AC3 packets
+ uint8_t subtype;
+ dstream >> subtype;
+ subtype = subtype >> 4;
+ if (subtype == 8) // AC3
+ audio_type = 5;
+ else
+ if (subtype == 10) // LPCM
+ audio_type = 7;
+
+ return len-1;
+}
+
+bool KMpegPlugin::find_mpeg_in_cdxa()
+{
+ int skip_len = 0;
+ uint32_t magic;
+ uint32_t data_len;
+ // search for data chunk
+ while (true) {
+ dstream >> magic;
+ if (magic != 0x64617461) { // "fmt "
+ skip_len = skip_riff_chunk();
+ if (!file.at(file.at()+skip_len)) return false;
+ continue;
+ } else {
+ // size of chunk
+ dstream >> data_len;
+ int block = 0;
+ // search for mpeg part
+ while(block < 32) {
+ // check for CDXA sync thingy
+ dstream >> magic;
+ // 00 ff ff ff ff ff ff ff ff ff ff ff 00
+ if (magic == 0x00ffffff) {
+// std::cerr << "Found CD sync" << std::endl;
+ // skip 20 bytes
+ if (!file.at(file.at()+20)) return false;
+ dstream >> magic;
+ if (magic == 0x000001ba) {
+// std::cerr << "Found CDXA mpeg" << std::endl;
+ return true;
+ }
+ else {
+// std::cerr << "CDXA block: #" <<block << ": " << magic << std::endl;
+ if (!file.at(file.at()+2324)) return false;
+ block++;
+ continue;
+ }
+ } else {
+// std::cerr << "Incorrect CDXA block" << std::endl;
+ // shouldn't happen
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
+
+bool KMpegPlugin::read_mpeg()
+{
+ mpeg = 0;
+ audio_type = 0;
+ audio_rate = 0;
+
+ uint32_t magic;
+ dstream >> magic;
+ if (magic == 0x52494646) // == "RIFF"
+ {
+ dstream >> magic;
+ dstream >> magic;
+ if (magic != 0x43445841) { // 0x43445841 == "CDXA"
+ kdDebug(7034) << "Unknown RIFF file" << endl;
+ return false;
+ } else {
+ if (!find_mpeg_in_cdxa()) return false;
+ }
+ }
+ else
+ if (magic != 0x000001ba) {
+ kdDebug(7034) << "Not a MPEG-PS file" << endl;
+ return false;
+ }
+// file.at(0);
+
+ uint8_t byte;
+ int skip_len = 0;
+ int state = 0;
+ int skimmed = 0;
+ int video_len = 0;
+ int searched = 0;
+ bool video_found = false, audio_found = false, gop_found = false;
+ // Search for MPEG packets
+ for(int i=0; i < 2048; i++) {
+ dstream >> byte;
+ skimmed++;
+ searched++;
+ if (video_len > 0) video_len--;
+ // Use a fast state machine to find 00 00 01 sync code
+ switch (state) {
+ case 0:
+ if (byte == 0)
+ state = 1;
+ else
+ state = 0;
+ break;
+ case 1:
+ if (byte == 0)
+ state = 2;
+ else
+ state = 0;
+ break;
+ case 2:
+ if (byte == 0)
+ state = 2;
+ else
+ if (byte == 1)
+ state = 3;
+ else
+ state = 0;
+ break;
+ case 3: {
+ skimmed -= 4;
+ if (skimmed) {
+// kdDebug(7034) << "Bytes skimmed:" << skimmed << endl;
+ skimmed = 0;
+ }
+// kdDebug(7034) << "Packet of type:" << TQString::number(byte,16) << endl;
+ switch (byte) {
+ case 0xb3:
+ if (video_found) break;
+ skip_len = parse_seq();
+ video_found = true;
+ video_len -= 8;
+ video_len -= skip_len;
+ break;
+ case 0xb5:
+ parse_seq_ext();
+ video_len -= 4;
+ break;
+ case 0xb8:
+ /*
+ if (!gop_found) {
+ start_time = parse_gop();
+ gop_found = true;
+ kdDebug(7034) << "start_time: " << start_time << endl;
+ }
+ */
+ /* nobreak */
+ case 0x00:
+ case 0x01:
+ // skip the rest of the video data
+ if (video_len > 0 && video_found)
+ skip_len = video_len;
+ break;
+ /*
+ case 0xb2:
+ skip_len = parse_user();
+ break;
+ */
+ case 0xba:
+ skip_len = 8;
+ break;
+ case 0xbe:
+ // padding
+ skip_len = skip_packet();
+ break;
+ case 0xe0:
+ // video data
+ if (video_found)
+ skip_len = skip_packet();
+ else
+ video_len = skip_packet();
+ break;
+ case 0xbd:
+ case 0xbf:
+ skip_len = parse_private();
+ break;
+ case 0xc0:
+ case 0xd0:
+ skip_len = parse_audio();
+ audio_found = true;
+ break;
+ default:
+// kdDebug(7034) << "Unhandled packet of type:" << TQString::number(byte,16) << endl;
+ break;
+ }
+ state = 0;
+ break;
+ }
+ }
+
+ if (video_found && audio_found /*&& gop_found*/) break;
+ if (skip_len) {
+ if (!file.at(file.at()+skip_len))
+ return false;
+ searched += skip_len;
+ skip_len = 0;
+ }
+ }
+ /*
+ if (skimmed)
+ kdDebug(7034) << "Bytes skimmed:" << skimmed << endl;
+ kdDebug(7034) << "Bytes searched:" << searched << endl;
+ */
+
+ if (mpeg == 0) {
+ kdDebug(7034) << "No sequence-start found" << endl;
+ return false;
+ }
+ return true;
+}
+
+// Search for the last GOP packet and read the time field
+void KMpegPlugin::read_length()
+{
+ end_time = 0;
+ uint8_t byte;
+ int state = 0;
+ // Search for the last gop
+ file.at(file.size()-1024);
+ for(int j=1; j<64; j++) {
+// dstream.setDevice(&file);
+// dstream.setByteOrder(TQDataStream::BigEndian);
+ for(int i=0; i<1024; i++) {
+ dstream >> byte;
+ switch (state) {
+ case 0:
+ if (byte == 0)
+ state = 1;
+ else
+ state = 0;
+ break;
+ case 1:
+ if (byte == 0)
+ state = 2;
+ else
+ state = 0;
+ case 2:
+ if (byte == 0)
+ state = 2;
+ else
+ if (byte == 1)
+ state = 3;
+ else
+ state = 0;
+ case 3:
+ if (byte == 0xb8) {
+ end_time = parse_gop();
+ kdDebug(7034) << "end_time: " << end_time << endl;
+ return;
+ }
+ state = 0;
+ }
+ }
+ state = 0;
+ file.at(file.size()-j*1024);
+ }
+ kdDebug(7034) << "No end GOP found" << endl;
+}
+
+bool KMpegPlugin::readInfo( KFileMetaInfo& info, uint /*what*/)
+{
+ if ( info.path().isEmpty() ) // remote file
+ return false;
+
+ file.setName(info.path());
+
+ // open file, set up stream and set endianness
+ if (!file.open(IO_ReadOnly))
+ {
+ kdDebug(7034) << "Couldn't open " << TQFile::encodeName(info.path()).data() << endl;
+ return false;
+ }
+
+ dstream.setDevice(&file);
+ dstream.setByteOrder(TQDataStream::BigEndian);
+
+ start_time = end_time = 0L;
+
+ if (!read_mpeg()) {
+ kdDebug(7034) << "read_mpeg() failed!" << endl;
+ }
+ else {
+ KFileMetaInfoGroup group = appendGroup(info, "Technical");
+
+ appendItem(group, "Frame rate", double(frame_rate));
+
+ appendItem(group, "Resolution", TQSize(horizontal_size, vertical_size));
+ /* The GOP timings are completely bogus
+ read_length();
+ if (end_time != 0) {
+ //long total_frames = end_time-start_time + 1;
+ long total_time = end_time;
+ appendItem(group, "Length", int(total_time));
+ }
+ // and so is bitrate
+ long total_time = file.size()/((bitrate+audio_rate)*50);
+ appendItem(group, "Length", int(total_time));
+ */
+ if (mpeg == 1)
+ appendItem(group, "Video codec", "MPEG-1");
+ else
+ appendItem(group, "Video codec", "MPEG-2");
+
+ switch (audio_type) {
+ case 1:
+ appendItem(group, "Audio codec", "MP1");
+ break;
+ case 2:
+ appendItem(group, "Audio codec", "MP2");
+ break;
+ case 3:
+ appendItem(group, "Audio codec", "MP3");
+ break;
+ case 5:
+ appendItem(group, "Audio codec", "AC3");
+ break;
+ case 7:
+ appendItem(group, "Audio codec", "PCM");
+ break;
+ default:
+ appendItem(group, "Audio codec", i18n("Unknown"));
+ }
+ // MPEG 1 also has an aspect ratio setting, but it works differently,
+ // and I am not sure if it is used.
+ if (mpeg == 2) {
+ switch (aspect_ratio) {
+ case 1:
+ appendItem(group, "Aspect ratio", i18n("default"));
+ break;
+ case 2:
+ appendItem(group, "Aspect ratio", "4/3");
+ break;
+ case 3:
+ appendItem(group, "Aspect ratio", "16/9");
+ break;
+ case 4:
+ appendItem(group, "Aspect ratio", "2.11/1");
+ break;
+ }
+ }
+ }
+
+ file.close();
+ return true;
+}
+
+#include "tdefile_mpeg.moc"