diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..5c98b42 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml \ No newline at end of file diff --git a/.idea/dictionaries/pragma.xml b/.idea/dictionaries/pragma.xml new file mode 100644 index 0000000..fcb7a84 --- /dev/null +++ b/.idea/dictionaries/pragma.xml @@ -0,0 +1,9 @@ + + + + idct + mpeg + yuvrgb + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d1e22ec --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..42f10a7 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/pyd2v.iml b/.idea/pyd2v.iml new file mode 100644 index 0000000..9b31378 --- /dev/null +++ b/.idea/pyd2v.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..27fecb3 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# pyd2v + +A Python Parser for DGMPGDec's D2V Project Files + +`pip install pyd2v` + +

+ +GitHub license +CodeFactor + +GitHub issues + +

+ +## Documentation + +### Quick Example + +``` +# pip install pyd2v +from pyd2v import D2V + +# ... + +# Choose Input File +input_file = "/home/user/Desktop/video.d2v" +# Parse Input File +d2v = D2V(filename=input_file) +# Print D2V Version and Settings Options, which will be shown with the accessible variable names. +print(d2v) +# Print Input Video Files +print(d2v.videos) +# Print Frame Rate +print(d2v.settings["Frame_Rate"]) +# Print first frame data +print(d2v.data[0]) +``` + +#### Accessible Variables + +A successful D2V parse will result in the following options accessible from the D2V object. + +- version: D2V version, `16` is currently the latest for the original DGIndex which was last updated in 2010. +- videos: List of the video file paths that were indexed by DGIndex. It will be just a filename if "Use Full Paths" was disabled in DGIndex. +- settings: Will return various user-provided and auto-evaluated settings based on input data. More information on Settings below. +- data: Indexing data of the MPEG video stream, Each entry is of an I frame which will describe the following non-I frames up to the next I frame. +- data_type: What type of video is most previlent, e.g. `88.4% FILM`, `PAL`, `99.9% NTSC`. + +#### Settings + +| Auto-evaluated Settings | Possible Values | Description | +| ----------------------- | -------------------------------------- | ---------------------------------------------------------------------------------- | +| Stream_Type | 0=Elementary Stream | Defines the type of MPEG stream. | +| | 1=Program Stream | | +| | 2=Transport Stream | | +| | 3=PVA Stream | | +| Transport_Packet_Size | [188, 204] | Specifies the size in bytes of the transport packets. Used only for Stream_Type=2. | +| MPEG_Type | 1=MPEG-1, 2=MPEG-2 | Defines the type of MPEG stream. | +| Aspect_Ratio | MPEG-2: "1:1", "4:3", "16:9", "2.21:1" | Defines the Aspect Ratio of the video specified in the MPEG stream. | +| | MPEG-1: "1:1", 0.6735, ["16:9", 625], | | +| | 0.7615, 0.8055, ["16:9", 525], 0.8935, | | +| | ["4:3", 625], 0.9815, 1.0255, 1.0695, | | +| | ["4:3", 525], 1.575, 1.2015 | | +| Picture_Size | [width, height] | Defines the size of the video _after_ clipping has been applied. | +| Frame_Rate | rate [num, den] | 'rate' defines output framerate \* 1000. | + +| User-specified Settings | Possible Values | Description | +| ----------------------- | ----------------------------------------- | ------------------------------------------------------------------------ | +| MPEG2_Transport_PID | {Video, Audio, RCR} | Selects the video/audio PIDs to be decoded. Used only for Stream_Type=2. | +| iDCT_Algorithm | 1=32-bit MMX | Defines the iDCT DGDecode will use to decode this video | +| | 2=32-bit SSEMMX | | +| | 3=32-bit SSE2MMX | | +| | 4=64-bit Floating Point | | +| | 5=64-bit IEEE-1180 Reference | | +| | 6=32-bit SSEMMX (Skal) | | +| | 7=32-bit Simple MMX (XviD) | | +| YUVRGB_Scale | 0=TV Scale | Defines the range DGDecode will use if RGB conversion is requested. | +| | 1=PC Scale | | +| Luminance_Filter | {Gamma, Offset} (range of +/- 256) | Defines values for DGIndex's Luminance_Filter. | +| Clipping | [ClipLeft,ClipRight,ClipTop,ClipBottom] | Defines values for Cropping lines of video. | +| Field_Operation | 0=Honor Pulldown Flags | Defines values for Field Operation. | +| | 1=Force Film | | +| | 2=Ignore Pulldown Flags | | +| Location | {StartFile,StartOffset,EndFile,EndOffset} | Defines start and end points for the video selection range. | diff --git a/pyd2v/__init__.py b/pyd2v/__init__.py new file mode 100644 index 0000000..210150b --- /dev/null +++ b/pyd2v/__init__.py @@ -0,0 +1,97 @@ +class D2V: + """ + Object containing information on D2V Project Files + """ + def __init__(self, filename): + """ + Parse a d2v file + :param str filename: path to the d2v file to be parsed + :rtype: :class:`D2V`. + :raises ValueError: if parsing fails + """ + # Header + self.version = None + self.videos = None + # Settings + self.settings = None + # Data + self.data = None + self.data_type = None + with open(filename, mode="r", encoding="utf-8") as f: + # Version + self.version = f.readline().strip() + if not self.version.startswith("DGIndexProjectFile"): + raise ValueError(f"Expected Version Header, received:\n\t{self.version}") + self.version = self.version[18:] # strip "DGIndexProjectFile" + # Videos + self.videos = [] + for n in range(int(f.readline().strip())): + self.videos.append(f.readline().strip()) + # Headers Terminate Check + if len(f.readline().strip()) > 0: + raise ValueError("Unexpected data after reading Header's Video List.") + # Settings + self.settings = {} + int_list = ["Stream_Type", "MPEG_Type", "iDCT_Algorithm", "YUVRGB_Scale", "Field_Operation"] + while True: + line = f.readline().strip() + # Settings Terminate Check + if len(line) == 0: + break + line = line.split("=", maxsplit=2) + self.settings[line[0]] = int(line[1]) if line[0] in int_list else line[1] + if self.settings["Stream_Type"] == 2: + self.settings["MPEG2_Transport_PID"] = self.settings["MPEG2_Transport_PID"].split(",") + self.settings["MPEG2_Transport_PID"] = { + "Video": float(self.settings["MPEG2_Transport_PID"][0]), + "Audio": float(self.settings["MPEG2_Transport_PID"][1]), + "PCR": float(self.settings["MPEG2_Transport_PID"][2]) + } + self.settings["Transport_Packet_Size"] = self.settings["Transport_Packet_Size"].split(",") + self.settings["Luminance_Filter"] = self.settings["Luminance_Filter"].split(",") + self.settings["Luminance_Filter"] = { + "Gamma": float(self.settings["Luminance_Filter"][0]), + "Offset": float(self.settings["Luminance_Filter"][1]) + } + self.settings["Clipping"] = [int(x) for x in self.settings["Clipping"].split(",")] + if "," in self.settings["Aspect_Ratio"]: + self.settings["Aspect_Ratio"] = [ + (x if ":" in x else float(x)) for x in self.settings["Aspect_Ratio"].split(",") + ] + elif ":" not in self.settings["Aspect_Ratio"]: + self.settings["Aspect_Ratio"] = float(self.settings["Aspect_Ratio"]) + self.settings["Picture_Size"] = [int(x) for x in self.settings["Picture_Size"].split("x")] + self.settings["Frame_Rate"] = self.settings["Frame_Rate"].split(" ") + self.settings["Frame_Rate"] = [ + self.settings["Frame_Rate"][0], + [int(x) for x in self.settings["Frame_Rate"][1].strip("()").split("/")] + ] + self.settings["Location"] = self.settings["Location"].split(",") + self.settings["Location"] = { + "StartFile": int(self.settings["Location"][0]), + "StartOffset": int(self.settings["Location"][1]), + "EndFile": int(self.settings["Location"][2]), + "EndOffset": int(self.settings["Location"][3]) + } + # Data + self.data = [] + while True: + line = f.readline().strip() + # Data Terminate Check + if len(line) == 0: + break + line = line.split(" ", maxsplit=7) + self.data.append({ + "info": line[0], + "matrix": line[1], + "file": line[2], + "position": line[3], + "skip": line[4], + "vob": line[5], + "cell": line[6], + "flags": line[7].split(" ") + }) + self.data_type = f.readline()[10:] + + def __repr__(self): + return f"" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f185d77 --- /dev/null +++ b/setup.py @@ -0,0 +1,24 @@ +from setuptools import setup, find_packages + +with open("README.md", "r") as f: + readme = f.read() + +setup( + name="pyd2v", + version="1.0.0-post2", + author="PHOENiX", + author_email="pragma.exe@gmail.com", + description="A Python Parser for DGMPGDec's D2V Project Files", + license="MIT", + long_description=readme, + long_description_content_type="text/markdown", + url="https://github.com/rlaPHOENiX/pyd2v", + packages=find_packages(), + install_requires=[], + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires=">=3.6", +)