Welcome to Doom9's Forum, THE in-place to be for everyone interested in DVD conversion.

Before you start posting please read the forum rules. By posting to this forum you agree to abide by the rules.

 

Go Back   Doom9's Forum > Programming and Hacking > Development

Reply
 
Thread Tools Search this Thread Display Modes
Old 1st November 2013, 20:08   #1  |  Link
KG6ZVP
Registered User
 
Join Date: Dec 2008
Posts: 5
Commercial Removal Software

Hi all,

I sat down yesterday and wrote out a rough program for doing commercial removal. I have a working binary of it, but I thought I'd just post the source (the source is newer than the binary).

A little bit about this program: It doesn't remove any commercials yet. As of the present, all it does is take a file with a list of black frames retrieved via avisynth script (also below) and generate a new avs with different sections of video labeled. I will continue working on this to come up with a complete solution (ie: I'd like to add in the ffmpeg libraries so that it could work directly on a broadcast video recording and generate the list of black frames itself as well as use the audio to help with the analysis)

Anyway, start by placing whatever file you want to analyze in this format:

Code:
LoadPlugin("GScript.dll")
Video=mpeg2source("video.d2v", info=3)
Audio=NicAC3Source("audio 3_2ch 384Kbps DELAY -541ms.ac3")
film = AudioDub(Video,Audio).DelayAudio(-.541)
film

filename = "Black_frames.txt"
current_frame=0
last_black=0
GScript("
	while(current_frame < framecount+1){
		if(AverageLuma(film) < 17){
			WriteFileIf(film, filename, "AverageLuma(film) < 17 && last_black<(current_frame-1)", "current_frame")
			last_black = current_frame
		}
	}
")
Open that video and let it play and it will generate a file with a list of black frames in it.

Format a script to "edit" into sections. Use the example below or write your own.
Example:
Code:
Video=mpeg2source("video.d2v", info=3)
Audio=NicAC3Source("audio 3_2ch 384Kbps DELAY -541ms.ac3")
film = AudioDub(Video,Audio).DelayAudio(-.541)
Then, use my program to write a script (note: at this stage, my program assumes that whatever avisynth script is being edited calls it's output by the name "film.")

Currently the program can be run in a few ways:
Usage:
commercialremover [input list of frames] [avs script to add to] [append pattern part 1] [append pattern part 2]
or
Usage:
commercialremover [input list of frames]

or
Usage:
commercialremover [input list of frames] [avs script to add to]
or
Usage:
commercialremover [input list of frames] [avs script to add to] A
note:^ the A means to append a subtitle to each section of video displaying the section number

Example:
commercialremover.exe Black_frames.txt t
est.avs A

The above command produced this file:
Code:
Video=mpeg2source("video.d2v", info=3)
Audio=NicAC3Source("audio 3_2ch 384Kbps DELAY -541ms.ac3")
film = AudioDub(Video,Audio).DelayAudio(-.541)

film.trim(0,94).subtitle("Section 0",align=5,size=256)
+ film.trim(99,603).subtitle("Section 1",align=5,size=256)
+ film.trim(604,717).subtitle("Section 2",align=5,size=256)
+ film.trim(722,1278).subtitle("Section 3",align=5,size=256)
+ film.trim(1278,1507).subtitle("Section 4",align=5,size=256)
+ film.trim(1533,11363).subtitle("Section 5",align=5,size=256)
I will edit this post and add my working binary as soon as it is uploaded.

Please comment and bring up some different usage scenarios. I would like to eventually add keyframe and audio silence detection so that it could detect scene changes as well as just commercial breaks.

By the way, I compiled this binary on an arch linux x64 dev server using this command line:
i486-mingw32-g++ -static-libgcc -static-libstdc++ main.cpp -o commercialremover.exe

Here is the source code as it stands now (binary was compiled yesterday so they are slightly divergent)
Code:
/* 
 * File:   main.cpp
 * Author: sam (kg6zvp@gmail)
 *
 * Created on 31. Oktober 2013, 04:29
 */

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>

using namespace std;

class Section{
public:
	int a;
	int z;
	bool isContent;
	Section(int beginning, int end, bool isContent){
		a = beginning;
		z = end;
		this->isContent = isContent;
	}
	operator char*(){
		char* output = new char[30];
		sprintf(output,"film.trim(%d,%d)",a,z);
		return output;
	}
	vector<int>* getPreviewFrames(){
		int length = z-a;
		vector<int>* output = new vector<int>();
		//Switch method
		/*switch(length){
			
		}*/
		//average method
		int segment = length / 5;
		int remainder = length % 5;
		int segments[5]; //set up array of segments
		segments[0] = 0;
		for(int i=1; i < 4; i++){//populate array
			segments[i] = segments[i-1]+segment;
		}
		segments[4] = segments[3]+remainder;
		output->push_back( a+segments[0] );
		//Iterate through array and don't add duplicates
		for(int i=1; i < 4; i++){
			if(segments[i-1]!=segments[i]){
				output->push_back( a+segments[i] );
			}
		}
		return output;
	}
};

bool useSTDout;
bool append;
bool preview;
bool middleFrame;
string appendA, appendB;
/*
 * 
 */
int main(int argc, char* argv[]){
	switch(argc){
	case 1:
		cerr << "please pass the file to read from as an argument!\n";
		cerr << "Usage:\n\tcommercialremover [input list of frames] [avs script to add to] [append pattern part 1] [append pattern part 2]\n";
		cerr << "\t\tor\nUsage:\n\tcommercialremover [input list of frames]";
		cerr << "\t\tor\nUsage:\n\tcommercialremover [input list of frames] [avs script to add to]";
		cerr << "\t\tor\nUsage:\n\tcommercialremover [input list of frames] [avs script to add to] A\nnote:^^the A means to append a subtitle to each section of video displaying the section number";
		return(1);
		break;
	case 2:
		useSTDout = true;
		break;
	case 3:
		useSTDout = false;
		cout << "Outputting to: " << argv[2] << "\n";
		break;
	case 4:
		if(argv[3][0]=='A' && argv[3][1] ==0){
			append=true;
			preview=false;
			middleFrame=false;
			appendA.assign(".subtitle(\"Section ");
			appendB.assign("\",align=5,size=256)");
		}else if(argv[3][0]=='P' && argv[3][1] ==0){
			append=false;
			preview=true;
			middleFrame=false;
		}else if(argv[3][0]=='M' && argv[3][1] ==0){
			append=false;
			preview=false;
			middleFrame=true;
		}
		break;
	case 5:
		appendA.assign(argv[4]);
		appendB.assign(argv[5]);
		cout << "Will append " << appendA << "[INT]" << appendB << "\n";
		break;
	default:
		cerr << "Too many argumeuts!\n";
		return(1);
		break;
	}
	fstream input;
	input.open(argv[1],fstream::in); //open input file for reading
	if(!input.is_open()){ //if input file couldn't be opened, print error and exit
		cerr << "Error opening file: " << argv[1] << "\n";
		return(1);
	}
	/******************************
	 * Read all integers into map *
         ******************************/
	vector<int> allIntegers;
	int tInt = 0; //temporary integer variable for storing sscanf output before writing to vector
	int i=0;
	string line;
	while(getline(input,line)){
		tInt = -1;
		sscanf(line.c_str(),"%d",&tInt);
		if(tInt != -1){ //if sscanf got useful input
			allIntegers.push_back(tInt);
			//allIntegers[i] = tInt;
			i++;
		}
	}
	/**************************
	 *  Process all integers  *
	 **************************/
	int sectionBegin=0;
	int sectionEnd=0;
	int prevBlackFrame = 0; //the integer of the last black frame (ie: not the end frame, but the previous)
	int currBlackFrame = 0;
	int nextBlackFrame = 0; //the integer of the next black frame
	vector<Section> sections;
	int sectionIndex = 0; //this keeps track of how many sections have been added to the map
	for(vector<int>::iterator it = allIntegers.begin(); it != allIntegers.end(); ++it){
		if(it == allIntegers.begin()){ //if this is the first black frame in the video
			if( (*it) > 0 ){ //if the first black frame isn't the first frame of the video
				sectionEnd = (*it)-1; //end the first section at the end of the last colorful frame
				sections.push_back(Section(sectionBegin, sectionEnd, true));
				sectionBegin = (*it); //mark the next section's beginning (a section with black frames)
			}
		}else{ //if this isn't the first black frame in the video
			currBlackFrame = *it;
			nextBlackFrame = *(it+1);
			if ( prevBlackFrame!=(currBlackFrame-1) && (currBlackFrame+1)==nextBlackFrame) { //if the black frame is at the beginning of a series of black frames
				sectionEnd = currBlackFrame - 1; //set the end of the last section containing content
				sections.push_back(Section(sectionBegin, sectionEnd, true)); //pass in true, because this section contains content
				sectionBegin = currBlackFrame; //set the beginning of the next section (containing all black frames) to the current black frame
			} else if ( prevBlackFrame==(currBlackFrame-1) && (currBlackFrame+1)!=nextBlackFrame ) { //if the black frame is at the end of a series of black frames
				sectionEnd = currBlackFrame; //set the sectionEnd equal to the current frame
				sections.push_back(Section(sectionBegin, sectionEnd, false)); //pass in false, because this section contains black frames
				sectionBegin = currBlackFrame + 1; //set the begining of the next section equal to the next framee (which contains content)
			} else if ( prevBlackFrame!=(currBlackFrame-1) && (currBlackFrame+1)!=nextBlackFrame ) { //if the black frame is alone between colored frames
				sectionEnd = currBlackFrame - 1; //set the end of the last section equal to the last frame with content
				sections.push_back(Section(sectionBegin, sectionEnd, true)); //pass in true, because this section contains content
				sectionBegin = currBlackFrame; //set the beginning of the next section (containing all black frames) to the current black frame
				sectionEnd = currBlackFrame; //set the end of this section to the same frame (that way any audio in that section can be added to or from the correct place)
				sections.push_back(Section(sectionBegin, sectionEnd, false)); //pass in false, because this section does not contain content
			}
		}
		prevBlackFrame = *it;
	}
	
	int itIndex=0;
	cout << "Writing output file: " << argv[2] << "\n";
	ofstream output;
	output.open(argv[2], ofstream::app);
	output.write("\n\n",6);
	if(preview){
		/***********************
		 *  Generate  Preview  *
		 *  and write to file  *
		 ***********************/
		string s;
		s.assign("null!");
		string itIndexS;
		itIndexS.assign("null!");
		vector<int>* previewFrames;
		int currentFrame=0;
		int numToWrite=0;
		for(vector<Section>::iterator it = sections.begin(); it !=sections.end(); ++it){
			if(it != sections.begin())
				output.write("+ ", 2);
			previewFrames = it->getPreviewFrames();
			for(vector<int>::iterator frame = previewFrames->begin(); frame != previewFrames->end(); ++frame){//loop through frames and add them
				if(frame != previewFrames->begin())
					output.write("+ ", 2);
				currentFrame = (*frame);
				char* buffer = new char[600];
				//printf("film.trim(%d , -1).subtitle(\"Section %d (%d)\", align=5, size=256)", currentFrame, itIndex, currentFrame);
				numToWrite = sprintf(buffer, "film.trim(%d , -1).subtitle(\"Section %d(%d)\", align=5, size=128)", currentFrame, itIndex, currentFrame);
				output.write(buffer, numToWrite);
			}
			
			output.write(" ", 1);
			itIndex++;
		}
		return 0;
	}else if(middleFrame){
		/***********************
		 *  Generate  Preview  *
		 *  and write to file  *
		 ***********************/
		string s;
		s.assign("null!");
		string itIndexS;
		itIndexS.assign("null!");
		vector<int>* previewFrames;
		int currentFrame=0;
		int numToWrite=0;
		for(vector<Section>::iterator it = sections.begin(); it !=sections.end(); ++it){
			if(it != sections.begin())
				output.write("+ ", 2);
			currentFrame = (*(it->getPreviewFrames()->begin()));
			char* buffer = new char[600];
			//printf("film.trim(%d , -1).subtitle(\"Section %d (%d)\", align=5, size=256)", currentFrame, itIndex, currentFrame);
			numToWrite = sprintf(buffer, "film.trim(%d , -1).subtitle(\"Section %d(%d)\", align=5, size=128)", currentFrame, itIndex, currentFrame);
			output.write(buffer, numToWrite);
			}
			
			output.write(" ", 1);
			itIndex++;
		}
		return 0;
	}
	
	/*******************************
	 *  Print all sections to ???  *
	 *******************************/
	if(useSTDout){
		cout << "\n\n";
		for(vector<Section>::iterator it = sections.begin(); it !=sections.end(); it++){
			if(it != sections.begin())
				cout << "+ ";
			cout << (char*)(*it);
			if(append){
				cout << appendA.c_str() << itIndex << appendB.c_str();
				if( (*it).isContent ){
					cout << "/*content*/";
				}else{
					cout << "/*BLACK*/";
				}
			}
			cout << " ";
			itIndex++;
		}
	}else{
		string s;
		s.assign("null!");
		string itIndexS;
		itIndexS.assign("null!");
		for(vector<Section>::iterator it = sections.begin(); it !=sections.end(); ++it){
			s.assign((char*)(*it));
			if(it != sections.begin())
				output.write("+ ", 2);
			output.write(s.c_str(),s.size());
			if(append){
				output.write(appendA.c_str(),appendA.size());
				stringstream ss;
				ss << itIndex;
				itIndexS.assign(ss.str());
				output.write(itIndexS.c_str(),itIndexS.size());
				output.write(appendB.c_str(),appendB.size());
			}
			output.write(" ", 1);
			itIndex++;
		}
	}
	return 0;
}

Last edited by KG6ZVP; 2nd November 2013 at 03:04. Reason: update source code
KG6ZVP is offline   Reply With Quote
Old 2nd November 2013, 19:55   #2  |  Link
turbojet
Registered User
 
Join Date: May 2008
Posts: 1,840
It's a very ambitious project and very difficult to achieve based on comskip's years of development and still does bad detects often. Some common scenarios where the current and planned features won't detect correctly:
1. No black frames between ads and show
2. Black frames in between ads
3. No audio silence between ads and show
4. Audio silence in between ads

Keyframe detection could possibly resolve issues 1 and 3 but not sure how 2 and 4 would be handled. Logo detection is another common way but there's issues with that too, for example: show segment starts with random black frames between scenes or artistic effect before the logo shows up, which in some cases can be 15-20 seconds. Another issue with auto detectors is they take a long time to process.

Not to try to shift you from your current work but if you are up to it, I do think VideoRedo is begging for competition. There's other cutters like avidemux but nothing else is frame accurate. A tool that's frame accurate (requires short encodes) that has options to demux audio and convert cc to srt could grab a lot of videoredo users' attentions. No need to have all the encoding output settings like videoredo, that devs spend so much time working on.
__________________
PC: FX-8320 GTS250 HTPC: G1610 GTX650
PotPlayer/MPC-BE LAVFilters MadVR-Bicubic75AR/Lanczos4AR/Lanczos4AR LumaSharpen -Strength0.9-Pattern3-Clamp0.1-OffsetBias2.0
turbojet is offline   Reply With Quote
Old 2nd November 2013, 21:54   #3  |  Link
KG6ZVP
Registered User
 
Join Date: Dec 2008
Posts: 5
I agree with you on all of those points, turbojet, and I'm glad I'm not the only one who thought of keyframe detection. The idea of this project is to generate a series of output files based on the detected scene changes so that people can join the files end to end afterwards or delete some of the files and just make a TV episode output to it's own folder with 001.mkv, 002.mkv, etc. so they can at least get rid of most of the commercials.

At the moment, I am really just cutting my teeth on this since it is something I wanted anyway (there are too many useful windows-only tools right now like MeGUI's avs cutter, etc.)

If you have any tips for me, I would really appreciate it as I am currently trying to understand the libav documentation well enough to eliminate avisynth from the picture for detecting the black frames.
KG6ZVP is offline   Reply With Quote
Reply

Tags
avisynth, development

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT +1. The time now is 18:49.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.