Pushed latest changes, add Telecine/Interlacing detection to ff_d2v.py
This commit is contained in:
		
							parent
							
								
									e06f1dfad5
								
							
						
					
					
						commit
						5e4d9d6965
					
				
					 11 changed files with 2403 additions and 2039 deletions
				
			
		
							
								
								
									
										10
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							|  | @ -138,3 +138,13 @@ dmypy.json | |||
| # Cython debug symbols | ||||
| cython_debug/ | ||||
| 
 | ||||
| 
 | ||||
| .vscode/* | ||||
| !.vscode/settings.json | ||||
| !.vscode/tasks.json | ||||
| !.vscode/launch.json | ||||
| !.vscode/extensions.json | ||||
| *.code-workspace | ||||
| 
 | ||||
| # Local History for Visual Studio Code | ||||
| .history/ | ||||
|  |  | |||
							
								
								
									
										20
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								README.md
									
										
									
									
									
								
							|  | @ -2,9 +2,27 @@ | |||
| 
 | ||||
| Choggbuster is a set of python scripts aimed at automated preprocessing of video DVDs for archival and filtering | ||||
| 
 | ||||
| # Requirements | ||||
| 
 | ||||
| - python (obviously) | ||||
| - libdvdcss (for decrypting copy protected DVDs) | ||||
| - libdvdnav (for streaming the VOBs to disk) | ||||
| - libdvdread (for reading decrypted data off of DVDs) | ||||
| - ffmpeg (for demuxing) | ||||
| - ccextractor (for extracting DVD Subtitles) | ||||
| 
 | ||||
| # Setup (Windows) | ||||
| 
 | ||||
| 1. Clone the repo | ||||
| 2. `pip install cffi tqdm` | ||||
| 3. Grab [libdvdread, libdvdnav](https://www.videolan.org/developers/libdvdnav.html) and [libdvdcss](https://www.videolan.org/developers/libdvdcss.html) from VLC and drop them next to `dvd_ripper.py` | ||||
| 4. `python dvd_ripper.py F:\` or `python dvd_ripper.py D:\path\to\DVD.ISO` | ||||
| 4. `python dvd_ripper.py F:\` or `python dvd_ripper.py D:\path\to\DVD.ISO` | ||||
| 5. this will create a folder `out` with a subfolder for the disc containing: | ||||
|     - JSON file with metadata for the DVD title (`XXXX.json` where `X` is the title number) | ||||
|     - demuxed streams (`tXXX_aYYY_Z_0xAAA.{ext}` where `X` is the title number, `Y` is the angle number `Z` is stream index and `AAA` is the stream id) | ||||
|         - `.m2v` for video | ||||
|         - `.ac3` or `.dtx` for audio | ||||
|         - `.sub` and `.idx` for subtitles | ||||
|         - `.srt` for captions | ||||
|     - `.d2v` file for use with AviSynth and Vapoursynth D2V reader | ||||
|     - `.info.json` file containing video stream metadata (cropping information, interlaced/progressive frame count and aspect ration information) | ||||
|  |  | |||
							
								
								
									
										688
									
								
								dvd_reader.h
									
										
									
									
									
								
							
							
						
						
									
										688
									
								
								dvd_reader.h
									
										
									
									
									
								
							|  | @ -1,344 +1,344 @@ | |||
| /*
 | ||||
|  * Copyright (C) 2001, 2002 Billy Biggs <vektor@dumbterm.net>, | ||||
|  *                          Håkan Hjort <d95hjort@dtek.chalmers.se>, | ||||
|  *                          Björn Englund <d4bjorn@dtek.chalmers.se> | ||||
|  * | ||||
|  * This file is part of libdvdread. | ||||
|  * | ||||
|  * libdvdread 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. | ||||
|  * | ||||
|  * libdvdread 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 libdvdread; if not, write to the Free Software Foundation, Inc., | ||||
|  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * The DVD access interface. | ||||
|  * | ||||
|  * This file contains the functions that form the interface to to | ||||
|  * reading files located on a DVD. | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * The current version. | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * The length of one Logical Block of a DVD. | ||||
|  */ | ||||
| #define DVD_VIDEO_LB_LEN 2048 | ||||
| 
 | ||||
| /**
 | ||||
|  * Maximum length of filenames allowed in UDF. | ||||
|  */ | ||||
| #define MAX_UDF_FILE_NAME_LEN 2048 | ||||
| 
 | ||||
| typedef long int off_t; | ||||
| 
 | ||||
| /**
 | ||||
|  * Opaque type that is used as a handle for one instance of an opened DVD. | ||||
|  */ | ||||
| typedef struct dvd_reader_s dvd_reader_t; | ||||
| typedef struct dvd_reader_device_s dvd_reader_device_t; | ||||
| 
 | ||||
| /**
 | ||||
|  * Opaque type for a file read handle, much like a normal fd or FILE *. | ||||
|  */ | ||||
| typedef struct dvd_file_s dvd_file_t; | ||||
| 
 | ||||
| struct dvd_reader_stream_cb | ||||
| { | ||||
|     int ( *pf_seek )  ( void *p_stream,  uint64_t i_pos); | ||||
|     int ( *pf_read )  ( void *p_stream, void* buffer, int i_read); | ||||
|     int ( *pf_readv ) ( void *p_stream, void *p_iovec, int i_blocks); | ||||
| }; | ||||
| typedef struct dvd_reader_stream_cb dvd_reader_stream_cb; | ||||
| 
 | ||||
| /**
 | ||||
|  * Custom logger callback for DVDOpen[Stream]2 | ||||
|  * @param private Handle as provided in Open functions | ||||
|  * @param level Log level | ||||
|  * @param fmt Format string | ||||
|  * @param args Arguments list | ||||
|  * pf_log(priv, level, fmt, args); | ||||
|  */ | ||||
| typedef enum | ||||
| { | ||||
|     DVD_LOGGER_LEVEL_INFO, | ||||
|     DVD_LOGGER_LEVEL_ERROR, | ||||
|     DVD_LOGGER_LEVEL_WARN, | ||||
|     DVD_LOGGER_LEVEL_DEBUG, | ||||
| } dvd_logger_level_t; | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|   // void ( *pf_log )  ( void *, dvd_logger_level_t, const char *, va_list );
 | ||||
|   void *pf_log; | ||||
| } dvd_logger_cb; | ||||
| 
 | ||||
| /**
 | ||||
|  * Public type that is used to provide statistics on a handle. | ||||
|  */ | ||||
| typedef struct { | ||||
|   off_t size;          /**< Total size of file in bytes */ | ||||
|   int nr_parts;        /**< Number of file parts */ | ||||
|   off_t parts_size[9]; /**< Size of each part in bytes */ | ||||
| } dvd_stat_t; | ||||
| 
 | ||||
| /**
 | ||||
|  * Opens a block device of a DVD-ROM file, or an image file, or a directory | ||||
|  * name for a mounted DVD or HD copy of a DVD. | ||||
|  * The second form of Open function (DVDOpenStream) can be used to | ||||
|  * provide custom stream_cb functions to access the DVD (see libdvdcss). | ||||
|  * | ||||
|  * If the given file is a block device, or is the mountpoint for a block | ||||
|  * device, then that device is used for CSS authentication using libdvdcss. | ||||
|  * If no device is available, then no CSS authentication is performed, | ||||
|  * and we hope that the image is decrypted. | ||||
|  * | ||||
|  * If the path given is a directory, then the files in that directory may be | ||||
|  * in any one of these formats: | ||||
|  * | ||||
|  *   path/VIDEO_TS/VTS_01_1.VOB | ||||
|  *   path/video_ts/vts_01_1.vob | ||||
|  *   path/VTS_01_1.VOB | ||||
|  *   path/vts_01_1.vob | ||||
|  * | ||||
|  * @param path Specifies the the device, file or directory to be used. | ||||
|  * @param stream is a private handle used by stream_cb | ||||
|  * @param stream_cb is a struct containing seek and read functions | ||||
|  * @return If successful a a read handle is returned. Otherwise 0 is returned. | ||||
|  * | ||||
|  * dvd = DVDOpen(path); | ||||
|  * dvd = DVDOpenStream(stream, &stream_cb); | ||||
|  */ | ||||
| dvd_reader_t *DVDOpen( const char * ); | ||||
| dvd_reader_t *DVDOpenStream( void *, dvd_reader_stream_cb * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Same as DVDOpen, but with private handle to be passed back on callbacks | ||||
|  * | ||||
|  * @param path Specifies the the device, file or directory to be used. | ||||
|  * @param priv is a private handle | ||||
|  * @param logcb is a custom logger callback struct, or NULL if none needed | ||||
|  * @param stream_cb is a struct containing seek and read functions | ||||
|  * @return If successful a a read handle is returned. Otherwise 0 is returned. | ||||
|  * | ||||
|  * dvd = DVDOpen2(priv, logcb, path); | ||||
|  * dvd = DVDOpenStream2(priv, logcb, &stream_cb); | ||||
|  */ | ||||
| dvd_reader_t *DVDOpen2( void *, const dvd_logger_cb *, const char * ); | ||||
| dvd_reader_t *DVDOpenStream2( void *, const dvd_logger_cb *, dvd_reader_stream_cb * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Closes and cleans up the DVD reader object. | ||||
|  * | ||||
|  * You must close all open files before calling this function. | ||||
|  * | ||||
|  * @param dvd A read handle that should be closed. | ||||
|  * | ||||
|  * DVDClose(dvd); | ||||
|  */ | ||||
| void DVDClose( dvd_reader_t * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * | ||||
|  */ | ||||
| typedef enum { | ||||
|   DVD_READ_INFO_FILE,        /**< VIDEO_TS.IFO  or VTS_XX_0.IFO (title) */ | ||||
|   DVD_READ_INFO_BACKUP_FILE, /**< VIDEO_TS.BUP  or VTS_XX_0.BUP (title) */ | ||||
|   DVD_READ_MENU_VOBS,        /**< VIDEO_TS.VOB  or VTS_XX_0.VOB (title) */ | ||||
|   DVD_READ_TITLE_VOBS        /**< VTS_XX_[1-9].VOB (title).  All files in
 | ||||
|                                   the title set are opened and read as a | ||||
|                                   single file. */ | ||||
| } dvd_read_domain_t; | ||||
| 
 | ||||
| /**
 | ||||
|  * Stats a file on the DVD given the title number and domain. | ||||
|  * The information about the file is stored in a dvd_stat_t | ||||
|  * which contains information about the size of the file and | ||||
|  * the number of parts in case of a multipart file and the respective | ||||
|  * sizes of the parts. | ||||
|  * A multipart file is for instance VTS_02_1.VOB, VTS_02_2.VOB, VTS_02_3.VOB | ||||
|  * The size of VTS_02_1.VOB will be stored in stat->parts_size[0], | ||||
|  * VTS_02_2.VOB in stat->parts_size[1], ... | ||||
|  * The total size (sum of all parts) is stored in stat->size and | ||||
|  * stat->nr_parts will hold the number of parts. | ||||
|  * Only DVD_READ_TITLE_VOBS (VTS_??_[1-9].VOB) can be multipart files. | ||||
|  * | ||||
|  * This function is only of use if you want to get the size of each file | ||||
|  * in the filesystem. These sizes are not needed to use any other | ||||
|  * functions in libdvdread. | ||||
|  * | ||||
|  * @param dvd  A dvd read handle. | ||||
|  * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0. | ||||
|  * @param domain Which domain. | ||||
|  * @param stat Pointer to where the result is stored. | ||||
|  * @return If successful 0, otherwise -1. | ||||
|  * | ||||
|  * int DVDFileStat(dvd, titlenum, domain, stat); | ||||
|  */ | ||||
| int DVDFileStat(dvd_reader_t *, int, dvd_read_domain_t, dvd_stat_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * Opens a file on the DVD given the title number and domain. | ||||
|  * | ||||
|  * If the title number is 0, the video manager information is opened | ||||
|  * (VIDEO_TS.[IFO,BUP,VOB]).  Returns a file structure which may be | ||||
|  * used for reads, or 0 if the file was not found. | ||||
|  * | ||||
|  * @param dvd  A dvd read handle. | ||||
|  * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0. | ||||
|  * @param domain Which domain. | ||||
|  * @return If successful a a file read handle is returned, otherwise 0. | ||||
|  * | ||||
|  * dvd_file = DVDOpenFile(dvd, titlenum, domain); */ | ||||
| dvd_file_t *DVDOpenFile( dvd_reader_t *, int, dvd_read_domain_t ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Closes a file and frees the associated structure. | ||||
|  * | ||||
|  * @param dvd_file  The file read handle to be closed. | ||||
|  * | ||||
|  * DVDCloseFile(dvd_file); | ||||
|  */ | ||||
| void DVDCloseFile( dvd_file_t * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Reads block_count number of blocks from the file at the given block offset. | ||||
|  * Returns number of blocks read on success, -1 on error.  This call is only | ||||
|  * for reading VOB data, and should not be used when reading the IFO files. | ||||
|  * When reading from an encrypted drive, blocks are decrypted using libdvdcss | ||||
|  * where required. | ||||
|  * | ||||
|  * @param dvd_file  A file read handle. | ||||
|  * @param offset Block offset from the start of the file to start reading at. | ||||
|  * @param block_count Number of block to read. | ||||
|  * @param data Pointer to a buffer to write the data into. | ||||
|  * @return Returns number of blocks read on success, -1 on error. | ||||
|  * | ||||
|  * blocks_read = DVDReadBlocks(dvd_file, offset, block_count, data); | ||||
|  */ | ||||
| ssize_t DVDReadBlocks( dvd_file_t *, int, size_t, unsigned char * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Seek to the given position in the file.  Returns the resulting position in | ||||
|  * bytes from the beginning of the file.  The seek position is only used for | ||||
|  * byte reads from the file, the block read call always reads from the given | ||||
|  * offset. | ||||
|  * | ||||
|  * @param dvd_file  A file read handle. | ||||
|  * @param seek_offset Byte offset from the start of the file to seek to. | ||||
|  * @return The resulting position in bytes from the beginning of the file. | ||||
|  * | ||||
|  * offset_set = DVDFileSeek(dvd_file, seek_offset); | ||||
|  */ | ||||
| int32_t DVDFileSeek( dvd_file_t *, int32_t ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Reads the given number of bytes from the file.  This call can only be used | ||||
|  * on the information files, and may not be used for reading from a VOB.  This | ||||
|  * reads from and increments the currrent seek position for the file. | ||||
|  * | ||||
|  * @param dvd_file  A file read handle. | ||||
|  * @param data Pointer to a buffer to write the data into. | ||||
|  * @param bytes Number of bytes to read. | ||||
|  * @return Returns number of bytes read on success, -1 on error. | ||||
|  * | ||||
|  * bytes_read = DVDReadBytes(dvd_file, data, bytes); | ||||
|  */ | ||||
| ssize_t DVDReadBytes( dvd_file_t *, void *, size_t ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Returns the file size in blocks. | ||||
|  * | ||||
|  * @param dvd_file  A file read handle. | ||||
|  * @return The size of the file in blocks, -1 on error. | ||||
|  * | ||||
|  * blocks = DVDFileSize(dvd_file); | ||||
|  */ | ||||
| ssize_t DVDFileSize( dvd_file_t * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get a unique 128 bit disc ID. | ||||
|  * This is the MD5 sum of VIDEO_TS.IFO and the VTS_0?_0.IFO files | ||||
|  * in title order (those that exist). | ||||
|  * If you need a 'text' representation of the id, print it as a | ||||
|  * hexadecimal number, using lowercase letters, discid[0] first. | ||||
|  * I.e. the same format as the command-line 'md5sum' program uses. | ||||
|  * | ||||
|  * @param dvd A read handle to get the disc ID from | ||||
|  * @param discid The buffer to put the disc ID into. The buffer must | ||||
|  *               have room for 128 bits (16 chars). | ||||
|  * @return 0 on success, -1 on error. | ||||
|  */ | ||||
| int DVDDiscID( dvd_reader_t *, unsigned char * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get the UDF VolumeIdentifier and VolumeSetIdentifier | ||||
|  * from the PrimaryVolumeDescriptor. | ||||
|  * | ||||
|  * @param dvd A read handle to get the disc ID from | ||||
|  * @param volid The buffer to put the VolumeIdentifier into. | ||||
|  *              The VolumeIdentifier is latin-1 encoded (8bit unicode) | ||||
|  *              null terminated and max 32 bytes (including '\0') | ||||
|  * @param volid_size No more than volid_size bytes will be copied to volid. | ||||
|  *                   If the VolumeIdentifier is truncated because of this | ||||
|  *                   it will still be null terminated. | ||||
|  * @param volsetid The buffer to put the VolumeSetIdentifier into. | ||||
|  *                 The VolumeIdentifier is 128 bytes as | ||||
|  *                 stored in the UDF PrimaryVolumeDescriptor. | ||||
|  *                 Note that this is not a null terminated string. | ||||
|  * @param volsetid_size At most volsetid_size bytes will be copied to volsetid. | ||||
|  * @return 0 on success, -1 on error. | ||||
|  */ | ||||
| int DVDUDFVolumeInfo( dvd_reader_t *, char *, unsigned int, | ||||
|                       unsigned char *, unsigned int ); | ||||
| 
 | ||||
| int DVDFileSeekForce( dvd_file_t *, int offset, int force_size); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get the ISO9660 VolumeIdentifier and VolumeSetIdentifier | ||||
|  * | ||||
|  * * Only use this function as fallback if DVDUDFVolumeInfo returns 0   * | ||||
|  * * this will happen on a disc mastered only with a iso9660 filesystem * | ||||
|  * * All video DVD discs have UDF filesystem                            * | ||||
|  * | ||||
|  * @param dvd A read handle to get the disc ID from | ||||
|  * @param volid The buffer to put the VolumeIdentifier into. | ||||
|  *              The VolumeIdentifier is coded with '0-9','A-Z','_' | ||||
|  *              null terminated and max 33 bytes (including '\0') | ||||
|  * @param volid_size No more than volid_size bytes will be copied to volid. | ||||
|  *                   If the VolumeIdentifier is truncated because of this | ||||
|  *                   it will still be null terminated. | ||||
|  * @param volsetid The buffer to put the VolumeSetIdentifier into. | ||||
|  *                 The VolumeIdentifier is 128 bytes as | ||||
|  *                 stored in the ISO9660 PrimaryVolumeDescriptor. | ||||
|  *                 Note that this is not a null terminated string. | ||||
|  * @param volsetid_size At most volsetid_size bytes will be copied to volsetid. | ||||
|  * @return 0 on success, -1 on error. | ||||
|  */ | ||||
| int DVDISOVolumeInfo( dvd_reader_t *, char *, unsigned int, | ||||
|                       unsigned char *, unsigned int ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Sets the level of caching that is done when reading from a device | ||||
|  * | ||||
|  * @param dvd A read handle to get the disc ID from | ||||
|  * @param level The level of caching wanted. | ||||
|  *             -1 - returns the current setting. | ||||
|  *              0 - UDF Cache turned off. | ||||
|  *              1 - (default level) Pointers to IFO files and some data from | ||||
|  *                  PrimaryVolumeDescriptor are cached. | ||||
|  * | ||||
|  * @return The level of caching. | ||||
|  */ | ||||
| int DVDUDFCacheLevel( dvd_reader_t *, int ); | ||||
| /*
 | ||||
|  * Copyright (C) 2001, 2002 Billy Biggs <vektor@dumbterm.net>, | ||||
|  *                          Håkan Hjort <d95hjort@dtek.chalmers.se>, | ||||
|  *                          Björn Englund <d4bjorn@dtek.chalmers.se> | ||||
|  * | ||||
|  * This file is part of libdvdread. | ||||
|  * | ||||
|  * libdvdread 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. | ||||
|  * | ||||
|  * libdvdread 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 libdvdread; if not, write to the Free Software Foundation, Inc., | ||||
|  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * The DVD access interface. | ||||
|  * | ||||
|  * This file contains the functions that form the interface to to | ||||
|  * reading files located on a DVD. | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * The current version. | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * The length of one Logical Block of a DVD. | ||||
|  */ | ||||
| #define DVD_VIDEO_LB_LEN 2048 | ||||
| 
 | ||||
| /**
 | ||||
|  * Maximum length of filenames allowed in UDF. | ||||
|  */ | ||||
| #define MAX_UDF_FILE_NAME_LEN 2048 | ||||
| 
 | ||||
| typedef long int off_t; | ||||
| 
 | ||||
| /**
 | ||||
|  * Opaque type that is used as a handle for one instance of an opened DVD. | ||||
|  */ | ||||
| typedef struct dvd_reader_s dvd_reader_t; | ||||
| typedef struct dvd_reader_device_s dvd_reader_device_t; | ||||
| 
 | ||||
| /**
 | ||||
|  * Opaque type for a file read handle, much like a normal fd or FILE *. | ||||
|  */ | ||||
| typedef struct dvd_file_s dvd_file_t; | ||||
| 
 | ||||
| struct dvd_reader_stream_cb | ||||
| { | ||||
|     int ( *pf_seek )  ( void *p_stream,  uint64_t i_pos); | ||||
|     int ( *pf_read )  ( void *p_stream, void* buffer, int i_read); | ||||
|     int ( *pf_readv ) ( void *p_stream, void *p_iovec, int i_blocks); | ||||
| }; | ||||
| typedef struct dvd_reader_stream_cb dvd_reader_stream_cb; | ||||
| 
 | ||||
| /**
 | ||||
|  * Custom logger callback for DVDOpen[Stream]2 | ||||
|  * @param private Handle as provided in Open functions | ||||
|  * @param level Log level | ||||
|  * @param fmt Format string | ||||
|  * @param args Arguments list | ||||
|  * pf_log(priv, level, fmt, args); | ||||
|  */ | ||||
| typedef enum | ||||
| { | ||||
|     DVD_LOGGER_LEVEL_INFO, | ||||
|     DVD_LOGGER_LEVEL_ERROR, | ||||
|     DVD_LOGGER_LEVEL_WARN, | ||||
|     DVD_LOGGER_LEVEL_DEBUG, | ||||
| } dvd_logger_level_t; | ||||
| 
 | ||||
| typedef struct | ||||
| { | ||||
|   // void ( *pf_log )  ( void *, dvd_logger_level_t, const char *, va_list );
 | ||||
|   void *pf_log; | ||||
| } dvd_logger_cb; | ||||
| 
 | ||||
| /**
 | ||||
|  * Public type that is used to provide statistics on a handle. | ||||
|  */ | ||||
| typedef struct { | ||||
|   off_t size;          /**< Total size of file in bytes */ | ||||
|   int nr_parts;        /**< Number of file parts */ | ||||
|   off_t parts_size[9]; /**< Size of each part in bytes */ | ||||
| } dvd_stat_t; | ||||
| 
 | ||||
| /**
 | ||||
|  * Opens a block device of a DVD-ROM file, or an image file, or a directory | ||||
|  * name for a mounted DVD or HD copy of a DVD. | ||||
|  * The second form of Open function (DVDOpenStream) can be used to | ||||
|  * provide custom stream_cb functions to access the DVD (see libdvdcss). | ||||
|  * | ||||
|  * If the given file is a block device, or is the mountpoint for a block | ||||
|  * device, then that device is used for CSS authentication using libdvdcss. | ||||
|  * If no device is available, then no CSS authentication is performed, | ||||
|  * and we hope that the image is decrypted. | ||||
|  * | ||||
|  * If the path given is a directory, then the files in that directory may be | ||||
|  * in any one of these formats: | ||||
|  * | ||||
|  *   path/VIDEO_TS/VTS_01_1.VOB | ||||
|  *   path/video_ts/vts_01_1.vob | ||||
|  *   path/VTS_01_1.VOB | ||||
|  *   path/vts_01_1.vob | ||||
|  * | ||||
|  * @param path Specifies the the device, file or directory to be used. | ||||
|  * @param stream is a private handle used by stream_cb | ||||
|  * @param stream_cb is a struct containing seek and read functions | ||||
|  * @return If successful a a read handle is returned. Otherwise 0 is returned. | ||||
|  * | ||||
|  * dvd = DVDOpen(path); | ||||
|  * dvd = DVDOpenStream(stream, &stream_cb); | ||||
|  */ | ||||
| dvd_reader_t *DVDOpen( const char * ); | ||||
| dvd_reader_t *DVDOpenStream( void *, dvd_reader_stream_cb * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Same as DVDOpen, but with private handle to be passed back on callbacks | ||||
|  * | ||||
|  * @param path Specifies the the device, file or directory to be used. | ||||
|  * @param priv is a private handle | ||||
|  * @param logcb is a custom logger callback struct, or NULL if none needed | ||||
|  * @param stream_cb is a struct containing seek and read functions | ||||
|  * @return If successful a a read handle is returned. Otherwise 0 is returned. | ||||
|  * | ||||
|  * dvd = DVDOpen2(priv, logcb, path); | ||||
|  * dvd = DVDOpenStream2(priv, logcb, &stream_cb); | ||||
|  */ | ||||
| dvd_reader_t *DVDOpen2( void *, const dvd_logger_cb *, const char * ); | ||||
| dvd_reader_t *DVDOpenStream2( void *, const dvd_logger_cb *, dvd_reader_stream_cb * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Closes and cleans up the DVD reader object. | ||||
|  * | ||||
|  * You must close all open files before calling this function. | ||||
|  * | ||||
|  * @param dvd A read handle that should be closed. | ||||
|  * | ||||
|  * DVDClose(dvd); | ||||
|  */ | ||||
| void DVDClose( dvd_reader_t * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * | ||||
|  */ | ||||
| typedef enum { | ||||
|   DVD_READ_INFO_FILE,        /**< VIDEO_TS.IFO  or VTS_XX_0.IFO (title) */ | ||||
|   DVD_READ_INFO_BACKUP_FILE, /**< VIDEO_TS.BUP  or VTS_XX_0.BUP (title) */ | ||||
|   DVD_READ_MENU_VOBS,        /**< VIDEO_TS.VOB  or VTS_XX_0.VOB (title) */ | ||||
|   DVD_READ_TITLE_VOBS        /**< VTS_XX_[1-9].VOB (title).  All files in
 | ||||
|                                   the title set are opened and read as a | ||||
|                                   single file. */ | ||||
| } dvd_read_domain_t; | ||||
| 
 | ||||
| /**
 | ||||
|  * Stats a file on the DVD given the title number and domain. | ||||
|  * The information about the file is stored in a dvd_stat_t | ||||
|  * which contains information about the size of the file and | ||||
|  * the number of parts in case of a multipart file and the respective | ||||
|  * sizes of the parts. | ||||
|  * A multipart file is for instance VTS_02_1.VOB, VTS_02_2.VOB, VTS_02_3.VOB | ||||
|  * The size of VTS_02_1.VOB will be stored in stat->parts_size[0], | ||||
|  * VTS_02_2.VOB in stat->parts_size[1], ... | ||||
|  * The total size (sum of all parts) is stored in stat->size and | ||||
|  * stat->nr_parts will hold the number of parts. | ||||
|  * Only DVD_READ_TITLE_VOBS (VTS_??_[1-9].VOB) can be multipart files. | ||||
|  * | ||||
|  * This function is only of use if you want to get the size of each file | ||||
|  * in the filesystem. These sizes are not needed to use any other | ||||
|  * functions in libdvdread. | ||||
|  * | ||||
|  * @param dvd  A dvd read handle. | ||||
|  * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0. | ||||
|  * @param domain Which domain. | ||||
|  * @param stat Pointer to where the result is stored. | ||||
|  * @return If successful 0, otherwise -1. | ||||
|  * | ||||
|  * int DVDFileStat(dvd, titlenum, domain, stat); | ||||
|  */ | ||||
| int DVDFileStat(dvd_reader_t *, int, dvd_read_domain_t, dvd_stat_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * Opens a file on the DVD given the title number and domain. | ||||
|  * | ||||
|  * If the title number is 0, the video manager information is opened | ||||
|  * (VIDEO_TS.[IFO,BUP,VOB]).  Returns a file structure which may be | ||||
|  * used for reads, or 0 if the file was not found. | ||||
|  * | ||||
|  * @param dvd  A dvd read handle. | ||||
|  * @param titlenum Which Video Title Set should be used, VIDEO_TS is 0. | ||||
|  * @param domain Which domain. | ||||
|  * @return If successful a a file read handle is returned, otherwise 0. | ||||
|  * | ||||
|  * dvd_file = DVDOpenFile(dvd, titlenum, domain); */ | ||||
| dvd_file_t *DVDOpenFile( dvd_reader_t *, int, dvd_read_domain_t ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Closes a file and frees the associated structure. | ||||
|  * | ||||
|  * @param dvd_file  The file read handle to be closed. | ||||
|  * | ||||
|  * DVDCloseFile(dvd_file); | ||||
|  */ | ||||
| void DVDCloseFile( dvd_file_t * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Reads block_count number of blocks from the file at the given block offset. | ||||
|  * Returns number of blocks read on success, -1 on error.  This call is only | ||||
|  * for reading VOB data, and should not be used when reading the IFO files. | ||||
|  * When reading from an encrypted drive, blocks are decrypted using libdvdcss | ||||
|  * where required. | ||||
|  * | ||||
|  * @param dvd_file  A file read handle. | ||||
|  * @param offset Block offset from the start of the file to start reading at. | ||||
|  * @param block_count Number of block to read. | ||||
|  * @param data Pointer to a buffer to write the data into. | ||||
|  * @return Returns number of blocks read on success, -1 on error. | ||||
|  * | ||||
|  * blocks_read = DVDReadBlocks(dvd_file, offset, block_count, data); | ||||
|  */ | ||||
| ssize_t DVDReadBlocks( dvd_file_t *, int, size_t, unsigned char * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Seek to the given position in the file.  Returns the resulting position in | ||||
|  * bytes from the beginning of the file.  The seek position is only used for | ||||
|  * byte reads from the file, the block read call always reads from the given | ||||
|  * offset. | ||||
|  * | ||||
|  * @param dvd_file  A file read handle. | ||||
|  * @param seek_offset Byte offset from the start of the file to seek to. | ||||
|  * @return The resulting position in bytes from the beginning of the file. | ||||
|  * | ||||
|  * offset_set = DVDFileSeek(dvd_file, seek_offset); | ||||
|  */ | ||||
| int32_t DVDFileSeek( dvd_file_t *, int32_t ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Reads the given number of bytes from the file.  This call can only be used | ||||
|  * on the information files, and may not be used for reading from a VOB.  This | ||||
|  * reads from and increments the currrent seek position for the file. | ||||
|  * | ||||
|  * @param dvd_file  A file read handle. | ||||
|  * @param data Pointer to a buffer to write the data into. | ||||
|  * @param bytes Number of bytes to read. | ||||
|  * @return Returns number of bytes read on success, -1 on error. | ||||
|  * | ||||
|  * bytes_read = DVDReadBytes(dvd_file, data, bytes); | ||||
|  */ | ||||
| ssize_t DVDReadBytes( dvd_file_t *, void *, size_t ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Returns the file size in blocks. | ||||
|  * | ||||
|  * @param dvd_file  A file read handle. | ||||
|  * @return The size of the file in blocks, -1 on error. | ||||
|  * | ||||
|  * blocks = DVDFileSize(dvd_file); | ||||
|  */ | ||||
| ssize_t DVDFileSize( dvd_file_t * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get a unique 128 bit disc ID. | ||||
|  * This is the MD5 sum of VIDEO_TS.IFO and the VTS_0?_0.IFO files | ||||
|  * in title order (those that exist). | ||||
|  * If you need a 'text' representation of the id, print it as a | ||||
|  * hexadecimal number, using lowercase letters, discid[0] first. | ||||
|  * I.e. the same format as the command-line 'md5sum' program uses. | ||||
|  * | ||||
|  * @param dvd A read handle to get the disc ID from | ||||
|  * @param discid The buffer to put the disc ID into. The buffer must | ||||
|  *               have room for 128 bits (16 chars). | ||||
|  * @return 0 on success, -1 on error. | ||||
|  */ | ||||
| int DVDDiscID( dvd_reader_t *, unsigned char * ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get the UDF VolumeIdentifier and VolumeSetIdentifier | ||||
|  * from the PrimaryVolumeDescriptor. | ||||
|  * | ||||
|  * @param dvd A read handle to get the disc ID from | ||||
|  * @param volid The buffer to put the VolumeIdentifier into. | ||||
|  *              The VolumeIdentifier is latin-1 encoded (8bit unicode) | ||||
|  *              null terminated and max 32 bytes (including '\0') | ||||
|  * @param volid_size No more than volid_size bytes will be copied to volid. | ||||
|  *                   If the VolumeIdentifier is truncated because of this | ||||
|  *                   it will still be null terminated. | ||||
|  * @param volsetid The buffer to put the VolumeSetIdentifier into. | ||||
|  *                 The VolumeIdentifier is 128 bytes as | ||||
|  *                 stored in the UDF PrimaryVolumeDescriptor. | ||||
|  *                 Note that this is not a null terminated string. | ||||
|  * @param volsetid_size At most volsetid_size bytes will be copied to volsetid. | ||||
|  * @return 0 on success, -1 on error. | ||||
|  */ | ||||
| int DVDUDFVolumeInfo( dvd_reader_t *, char *, unsigned int, | ||||
|                       unsigned char *, unsigned int ); | ||||
| 
 | ||||
| int DVDFileSeekForce( dvd_file_t *, int offset, int force_size); | ||||
| 
 | ||||
| /**
 | ||||
|  * Get the ISO9660 VolumeIdentifier and VolumeSetIdentifier | ||||
|  * | ||||
|  * * Only use this function as fallback if DVDUDFVolumeInfo returns 0   * | ||||
|  * * this will happen on a disc mastered only with a iso9660 filesystem * | ||||
|  * * All video DVD discs have UDF filesystem                            * | ||||
|  * | ||||
|  * @param dvd A read handle to get the disc ID from | ||||
|  * @param volid The buffer to put the VolumeIdentifier into. | ||||
|  *              The VolumeIdentifier is coded with '0-9','A-Z','_' | ||||
|  *              null terminated and max 33 bytes (including '\0') | ||||
|  * @param volid_size No more than volid_size bytes will be copied to volid. | ||||
|  *                   If the VolumeIdentifier is truncated because of this | ||||
|  *                   it will still be null terminated. | ||||
|  * @param volsetid The buffer to put the VolumeSetIdentifier into. | ||||
|  *                 The VolumeIdentifier is 128 bytes as | ||||
|  *                 stored in the ISO9660 PrimaryVolumeDescriptor. | ||||
|  *                 Note that this is not a null terminated string. | ||||
|  * @param volsetid_size At most volsetid_size bytes will be copied to volsetid. | ||||
|  * @return 0 on success, -1 on error. | ||||
|  */ | ||||
| int DVDISOVolumeInfo( dvd_reader_t *, char *, unsigned int, | ||||
|                       unsigned char *, unsigned int ); | ||||
| 
 | ||||
| /**
 | ||||
|  * Sets the level of caching that is done when reading from a device | ||||
|  * | ||||
|  * @param dvd A read handle to get the disc ID from | ||||
|  * @param level The level of caching wanted. | ||||
|  *             -1 - returns the current setting. | ||||
|  *              0 - UDF Cache turned off. | ||||
|  *              1 - (default level) Pointers to IFO files and some data from | ||||
|  *                  PrimaryVolumeDescriptor are cached. | ||||
|  * | ||||
|  * @return The level of caching. | ||||
|  */ | ||||
| int DVDUDFCacheLevel( dvd_reader_t *, int ); | ||||
|  |  | |||
							
								
								
									
										164
									
								
								dvd_ripper.py
									
										
									
									
									
								
							
							
						
						
									
										164
									
								
								dvd_ripper.py
									
										
									
									
									
								
							|  | @ -1,65 +1,99 @@ | |||
| import cffi | ||||
| import os | ||||
| import sys | ||||
| import time | ||||
| from dvdnav import DVDNav,DVDError | ||||
| from dvdread import DVDRead | ||||
| import subprocess as SP | ||||
| import json | ||||
| from glob import glob | ||||
| import itertools as ITT | ||||
| from vob_demux import demux | ||||
| from ff_d2v import make_d2v | ||||
| 
 | ||||
| def loadlib(dll_path, *includes, **kwargs): | ||||
|     ffi = cffi.FFI() | ||||
|     for include in includes: | ||||
|         ffi.cdef(open(include).read(), kwargs) | ||||
|     return ffi, ffi.dlopen(dll_path) | ||||
| 
 | ||||
| for dvd_path in ITT.chain.from_iterable(map(glob,sys.argv[1:])): | ||||
|     r = DVDRead(dvd_path) | ||||
|     # r.grab_ifos() | ||||
|     # r.grab_vobs() | ||||
|     # exit() | ||||
| 
 | ||||
|     out_folder = os.path.join( | ||||
|         "out", "_".join([r.disc_id, r.udf_disc_name or r.iso_disc_name]).replace(" ", "_") | ||||
|     ) | ||||
|     os.makedirs(out_folder, exist_ok=True) | ||||
|     d = DVDNav(dvd_path) | ||||
|     to_demux = [] | ||||
|     for k, v in d.titles.items(): | ||||
|         v["duration"] = v["duration"].total_seconds() | ||||
|         v["chapters"] = [c.total_seconds() for c in v["chapters"]] | ||||
|         d.titles[k] = v | ||||
|         with open(os.path.join(out_folder, f"{k:03}.json"), "w") as fh: | ||||
|             json.dump(v, fh) | ||||
|         for a in range(0,99): | ||||
|             block=0 | ||||
|             outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob") | ||||
|             to_demux.append(outfile) | ||||
|             fh = open(outfile, "wb") | ||||
|             try: | ||||
|                 for block in d.get_blocks(k, a): | ||||
|                     if isinstance(block, int): | ||||
|                         outfile = os.path.join(out_folder, f"t{k:03}_a{a:03}_b{block:03}.vob") | ||||
|                         to_demux.append(outfile) | ||||
|                         if fh: | ||||
|                             fh.close() | ||||
|                         fh = open(outfile, "wb") | ||||
|                     else: | ||||
|                         fh.write(block) | ||||
|             except DVDError as e: | ||||
|                 if str(e)!="Invalid angle specified!": | ||||
|                     raise | ||||
|             if fh.tell()==0: | ||||
|                 fh.close() | ||||
|                 os.unlink(fh.name) | ||||
|                 while fh.name in to_demux: | ||||
|                     to_demux.remove(fh.name) | ||||
|     for file in to_demux: | ||||
|         demux(file) | ||||
|         os.unlink(file) | ||||
|     for file in glob(os.path.join(out_folder,"*.m2v")): | ||||
|         make_d2v(file) | ||||
| import itertools as ITT | ||||
| import json | ||||
| import os | ||||
| import subprocess as SP | ||||
| import sys | ||||
| import time | ||||
| from glob import glob | ||||
| 
 | ||||
| import cffi | ||||
| from datetime import timedelta | ||||
| from dvdnav import DVDError, DVDNav | ||||
| from dvdread import DVDRead | ||||
| from ff_d2v import make_d2v, make_meta | ||||
| from vob_demux import demux | ||||
| 
 | ||||
| def close_file_del_if_empty(fh): | ||||
|     if not fh: | ||||
|         return False | ||||
|     if fh.tell() == 0: | ||||
|         fh.close() | ||||
|         os.unlink(fh.name) | ||||
|         return False | ||||
|     else: | ||||
|         fh.close() | ||||
|         return True | ||||
| 
 | ||||
| 
 | ||||
| dur_thr = 60.0 | ||||
| 
 | ||||
| def process_m2v_files(path): | ||||
|     for file in glob(os.path.join(path,"**", "*.m2v")): | ||||
|         make_meta(file) | ||||
|         make_d2v(file) | ||||
| 
 | ||||
| 
 | ||||
| for dvd_path in ITT.chain.from_iterable(map(glob, sys.argv[1:])): | ||||
|     r = DVDRead(dvd_path) | ||||
|     # r.grab_ifos() | ||||
|     # r.grab_vobs() | ||||
|     # exit() | ||||
|     if os.path.isfile(dvd_path): | ||||
|         basename = os.path.splitext(os.path.basename(dvd_path))[0] | ||||
|     else: | ||||
|         basename = r.iso_disc_name or r.udf_disc_name | ||||
|     base_dir = os.path.join("out", "_".join([basename, r.disc_id]).replace(" ", "_")) | ||||
|     if os.path.isdir(base_dir): | ||||
|         print(f"Output foldrer {base_dir} exists, remove to re-rip DVD") | ||||
|         process_m2v_files(base_dir) | ||||
|         continue | ||||
|     os.makedirs(base_dir, exist_ok=True) | ||||
|     d = DVDNav(dvd_path) | ||||
|     to_demux = [] | ||||
|     for k, v in d.titles.items(): | ||||
|         out_folder=os.path.join(base_dir,f"t{k:03}") | ||||
|         v["duration"] = v["duration"].total_seconds() | ||||
|         if v["chapters"]: | ||||
|             v["chapters"] = [0.0]+[c.total_seconds() for c in v["chapters"]] | ||||
|             avg_chapter_len = v["duration"] / len(v["chapters"]) | ||||
|             # if avg_chapter_len<10: | ||||
|             #     continue | ||||
|         d.titles[k] = v | ||||
|         # if not v.get('audio'): | ||||
|         #     print(f"[{k}|0] Skipping title {k} because it has no audio tracks") | ||||
|         #     continue | ||||
|         # if not v.get('vts'): | ||||
|         #     print(f"[{k}|0] Skipping title {k} because it has no title sets") | ||||
|         #     continue | ||||
|         if v["duration"] < dur_thr: | ||||
|             print( | ||||
|                 f"[{k}|0] Skipping title {k} because it is shorter than {dur_thr} seconds ({v['duration']} seconds)" | ||||
|             ) | ||||
|             continue | ||||
|         os.makedirs(out_folder, exist_ok=True) | ||||
|         with open(os.path.join(out_folder, f"title.json"), "w") as fh: | ||||
|             json.dump(d.titles[k], fh, indent=4) | ||||
|         with open(os.path.join(out_folder, f"chapters.txt"), "w") as fh: | ||||
|             if set(v["chapters"])==set([0.0]): | ||||
|                 continue | ||||
|             for n,t in enumerate(v["chapters"],1): | ||||
|                 if abs(t-v["duration"])<1.0: | ||||
|                     continue | ||||
|                 print(f"CHAPTER{n:02}={timedelta(seconds=t)}",file=fh) | ||||
|                 print(f"CHAPTER{n:02}NAME=Chapter {n}",file=fh) | ||||
|         for a in range(0, 99): | ||||
|             outfile = os.path.join(out_folder, f"{a:03}.vob") | ||||
|             to_demux.append(outfile) | ||||
|             fh = open(outfile, "wb") | ||||
|             try: | ||||
|                 for block in d.get_blocks(k, a): | ||||
|                     fh.write(block) | ||||
|             except DVDError as e: | ||||
|                 if str(e) != "Invalid angle specified!": | ||||
|                     raise | ||||
|             close_file_del_if_empty(fh) | ||||
|     to_demux = list(filter(os.path.isfile, to_demux)) | ||||
|     for file in to_demux: | ||||
|         demux(file) | ||||
|         os.unlink(file) | ||||
|     process_m2v_files(base_dir) | ||||
|  |  | |||
							
								
								
									
										18
									
								
								dvdcss.h
									
										
									
									
									
								
							
							
						
						
									
										18
									
								
								dvdcss.h
									
										
									
									
									
								
							|  | @ -1,10 +1,10 @@ | |||
| typedef struct dvdcss_s * dvdcss_t; | ||||
| typedef struct dvdcss_stream_cb dvdcss_stream_cb; | ||||
| dvdcss_t dvdcss_open (const char *psz_target); | ||||
| dvdcss_t dvdcss_open_stream (void *p_stream, dvdcss_stream_cb *p_stream_cb); | ||||
| int dvdcss_close (dvdcss_t); | ||||
| int dvdcss_seek (dvdcss_t, int i_blocks, int i_flags); | ||||
| int dvdcss_read (dvdcss_t, void *p_buffer, int i_blocks, int i_flags); | ||||
| int dvdcss_readv(dvdcss_t, void *p_iovec, int i_blocks, int i_flags); | ||||
| const char* dvdcss_error (const dvdcss_t); | ||||
| typedef struct dvdcss_s * dvdcss_t; | ||||
| typedef struct dvdcss_stream_cb dvdcss_stream_cb; | ||||
| dvdcss_t dvdcss_open (const char *psz_target); | ||||
| dvdcss_t dvdcss_open_stream (void *p_stream, dvdcss_stream_cb *p_stream_cb); | ||||
| int dvdcss_close (dvdcss_t); | ||||
| int dvdcss_seek (dvdcss_t, int i_blocks, int i_flags); | ||||
| int dvdcss_read (dvdcss_t, void *p_buffer, int i_blocks, int i_flags); | ||||
| int dvdcss_readv(dvdcss_t, void *p_iovec, int i_blocks, int i_flags); | ||||
| const char* dvdcss_error (const dvdcss_t); | ||||
| int dvdcss_is_scrambled (dvdcss_t); | ||||
							
								
								
									
										585
									
								
								dvdnav.py
									
										
									
									
									
								
							
							
						
						
									
										585
									
								
								dvdnav.py
									
										
									
									
									
								
							|  | @ -1,265 +1,320 @@ | |||
| import cffi | ||||
| import os | ||||
| import functools | ||||
| from datetime import timedelta | ||||
| from tqdm import tqdm | ||||
| from dvdread import DVDRead | ||||
| 
 | ||||
| 
 | ||||
| def loadlib(dll_path, *includes, **kwargs): | ||||
|     ffi = cffi.FFI() | ||||
|     for include in includes: | ||||
|         ffi.cdef(open(include).read(), kwargs) | ||||
|     return ffi, ffi.dlopen(dll_path) | ||||
| 
 | ||||
| 
 | ||||
| class DVDError(Exception): | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class DVDNav(object): | ||||
|     def __init__(self, path, verbose=None, method="disc"): | ||||
|         if verbose is None: | ||||
|             os.environ.pop("DVDCSS_VERBOSE", None) | ||||
|         else: | ||||
|             os.environ["DVDCSS_VERBOSE"] = str(verbose) | ||||
|         os.environ["DVDCSS_METHOD"] = method | ||||
|         self.dvd = None | ||||
|         self.ffi, self.lib = loadlib( | ||||
|             "libdvdnav-4.dll", | ||||
|             "dvd_types.h", | ||||
|             "dvd_reader.h", | ||||
|             "ifo_types.h", | ||||
|             "nav_types.h", | ||||
|             "dvdnav_events.h", | ||||
|             "dvdnav.h", | ||||
|             pack=True, | ||||
|         ) | ||||
|         self.path = path | ||||
|         self.titles = {} | ||||
|         self.open(path) | ||||
| 
 | ||||
|     def __del__(self): | ||||
|         self.__check_error(self.lib.dvdnav_close(self.dvd)) | ||||
|         self.dvd = None | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self) | ||||
| 
 | ||||
|     def get_blocks(self, title, angle=1, slang=None): | ||||
|         self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1)) | ||||
|         self.__check_error(self.lib.dvdnav_title_play(self.dvd, title)) | ||||
|         curr_angle = self.ffi.new("int32_t*", 0) | ||||
|         num_angles = self.ffi.new("int32_t*", 0) | ||||
|         self.__check_error( | ||||
|             self.lib.dvdnav_get_angle_info(self.dvd, curr_angle, num_angles) | ||||
|         ) | ||||
|         if angle != 0: | ||||
|             if angle < 1 or angle > num_angles[0]: | ||||
|                 raise DVDError("Invalid angle specified!") | ||||
|             if angle != curr_angle[0]: | ||||
|                 self.__check_error(self.lib.dvdnav_angle_change(self.dvd, angle)) | ||||
|         if slang is not None: | ||||
|             self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd, slang)) | ||||
|         event = self.lib.DVDNAV_NOP | ||||
|         buf = self.ffi.new("char[]", 4096) | ||||
|         ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP) | ||||
|         size = self.ffi.new("int32_t*", 0) | ||||
|         pos = self.ffi.new("uint32_t*", 0) | ||||
|         total_size = self.ffi.new("uint32_t*", 0) | ||||
|         domains = { | ||||
|             1: "FirstPlay", | ||||
|             2: "VTSTitle", | ||||
|             4: "VMGM", | ||||
|             8: "VTSMenu", | ||||
|         } | ||||
|         events = { | ||||
|             0: "DVDNAV_BLOCK_OK", | ||||
|             1: "DVDNAV_NOP", | ||||
|             2: "DVDNAV_STILL_FRAME", | ||||
|             3: "DVDNAV_SPU_STREAM_CHANGE", | ||||
|             4: "DVDNAV_AUDIO_STREAM_CHANGE", | ||||
|             5: "DVDNAV_VTS_CHANGE", | ||||
|             6: "DVDNAV_CELL_CHANGE", | ||||
|             7: "DVDNAV_NAV_PACKET", | ||||
|             8: "DVDNAV_STOP", | ||||
|             9: "DVDNAV_HIGHLIGHT", | ||||
|             10: "DVDNAV_SPU_CLUT_CHANGE", | ||||
|             12: "DVDNAV_HOP_CHANNEL", | ||||
|             13: "DVDNAV_WAIT", | ||||
|         } | ||||
|         progbar = tqdm( | ||||
|             unit_divisor=1024, | ||||
|             unit_scale=True, | ||||
|             unit="iB", | ||||
|             desc="Ripping DVD", | ||||
|             disable=False, | ||||
|         ) | ||||
|         ripped = set() | ||||
|         current_vts = None | ||||
|         current_cell = None | ||||
|         current_pg = None | ||||
|         while True: | ||||
|             self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size)) | ||||
|             if ( | ||||
|                 self.lib.dvdnav_get_position(self.dvd, pos, total_size) | ||||
|                 == self.lib.DVDNAV_STATUS_OK | ||||
|             ): | ||||
|                 progbar.total = total_size[0] * 2048 | ||||
|                 progbar.n = max(progbar.n, min(progbar.total, pos[0] * 2048)) | ||||
|                 progbar.update(0) | ||||
|                 progbar.set_postfix( | ||||
|                     vts=current_vts, | ||||
|                     cell=current_cell, | ||||
|                     pg=current_pg, | ||||
|                     angle=angle, | ||||
|                     title=title, | ||||
|                 ) | ||||
|             # print("Got event:",events.get(ev[0],ev[0]),size[0]) | ||||
|             if ev[0] in [ | ||||
|                 self.lib.DVDNAV_SPU_CLUT_CHANGE, | ||||
|                 self.lib.DVDNAV_HOP_CHANNEL, | ||||
|                 self.lib.DVDNAV_NOP, | ||||
|                 self.lib.DVDNAV_HIGHLIGHT, | ||||
|             ]: | ||||
|                 continue | ||||
|             elif ev[0] == self.lib.DVDNAV_BLOCK_OK: | ||||
|                 yield self.ffi.buffer(buf, size[0])[:] | ||||
|             elif ev[0] == self.lib.DVDNAV_STOP: | ||||
|                 progbar.write(f"[{title}|{angle}] Stop") | ||||
|                 break | ||||
|             elif ev[0] == self.lib.DVDNAV_NAV_PACKET: | ||||
|                 pass | ||||
|             elif ev[0] == self.lib.DVDNAV_STILL_FRAME: | ||||
|                 self.__check_error(self.lib.dvdnav_still_skip(self.dvd)) | ||||
|             elif ev[0] == self.lib.DVDNAV_WAIT: | ||||
|                 self.__check_error(self.lib.dvdnav_wait_skip(self.dvd)) | ||||
|             elif ev[0] == self.lib.DVDNAV_SPU_STREAM_CHANGE: | ||||
|                 pass | ||||
|             elif ev[0] == self.lib.DVDNAV_AUDIO_STREAM_CHANGE: | ||||
|                 audio = self.ffi.cast("dvdnav_audio_stream_change_event_t*", buf) | ||||
|             elif ev[0] == self.lib.DVDNAV_CELL_CHANGE: | ||||
|                 cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf) | ||||
|                 current_cell = cell.cellN | ||||
|                 current_pg = cell.pgN | ||||
|                 progbar.write( | ||||
|                     f"[{title}|{angle}] Cell: {cell.cellN} ({cell.cell_start}-{cell.cell_start+cell.cell_length}), PG: {cell.pgN} ({cell.pg_start}-{cell.pg_start+cell.pg_length})" | ||||
|                 ) | ||||
|             elif ev[0] == self.lib.DVDNAV_VTS_CHANGE: | ||||
|                 vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf) | ||||
|                 new_vts = (vts.new_vtsN, vts.new_domain) | ||||
|                 ripped.add((vts.old_vtsN, vts.old_domain)) | ||||
|                 # progbar.write(f"[{title}|{angle}] VTS: {vts.old_vtsN} ({vts.old_domain} {old_domain}) -> {vts.new_vtsN} ({vts.new_domain} {new_domain})") | ||||
|                 if new_vts in ripped:  # looped | ||||
|                     progbar.write(f"[{title}|{angle}] Looped!") | ||||
|                     break | ||||
|                 current_vts = (vts.new_vtsN, vts.new_domain) | ||||
|                 if vts.new_domain == 8:  # back to menu | ||||
|                     progbar.write(f"[{title}|{angle}] Back to menu!") | ||||
|                     break | ||||
|                 yield vts.new_vtsN | ||||
|             else: | ||||
|                 progbar.write( | ||||
|                     f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}" | ||||
|                 ) | ||||
|         self.__check_error(self.lib.dvdnav_stop(self.dvd)) | ||||
| 
 | ||||
|     def __check_error(self, ret): | ||||
|         if ret == self.lib.DVDNAV_STATUS_ERR: | ||||
|             if self.dvd: | ||||
|                 err = self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd)) | ||||
|                 raise DVDError(err) | ||||
|             raise DVDError("Unknown error") | ||||
| 
 | ||||
|     def __get_titles(self): | ||||
|         titles = self.ffi.new("int32_t*", 0) | ||||
|         p_times = self.ffi.new("uint64_t[]", 512) | ||||
|         times = self.ffi.new("uint64_t**", p_times) | ||||
|         duration = self.ffi.new("uint64_t*", 0) | ||||
|         titles = self.ffi.new("int32_t*", 0) | ||||
|         self.lib.dvdnav_get_number_of_titles(self.dvd, titles) | ||||
|         num_titles = titles[0] | ||||
|         for title in range(0, num_titles + 1): | ||||
|             if self.lib.dvdnav_get_number_of_parts(self.dvd, title, titles) == 0: | ||||
|                 continue | ||||
|             num_parts = titles[0] | ||||
|             self.lib.dvdnav_get_number_of_angles(self.dvd, title, titles) | ||||
|             num_angles = titles[0] | ||||
|             num_chapters = self.lib.dvdnav_describe_title_chapters( | ||||
|                 self.dvd, title, times, duration | ||||
|             ) | ||||
|             if duration[0] == 0: | ||||
|                 continue | ||||
|             chapters = [] | ||||
|             for t in range(num_chapters): | ||||
|                 chapters.append(timedelta(seconds=times[0][t] / 90000)) | ||||
|             self.titles[title] = { | ||||
|                 "parts": num_parts, | ||||
|                 "angles": num_angles, | ||||
|                 "duration": timedelta(seconds=duration[0] / 90000), | ||||
|                 "chapters": chapters, | ||||
|             } | ||||
| 
 | ||||
|     def __get_info(self): | ||||
|         s = self.ffi.new("char**", self.ffi.NULL) | ||||
|         self.lib.dvdnav_get_title_string(self.dvd, s) | ||||
|         self.title = str(self.ffi.string(s[0]), "utf8").strip() or None | ||||
|         self.lib.dvdnav_get_serial_string(self.dvd, s) | ||||
|         self.serial = str(self.ffi.string(s[0]), "utf8").strip() or None | ||||
|         self.__get_titles() | ||||
| 
 | ||||
|     def open(self, path): | ||||
|         audio_attrs = self.ffi.new("audio_attr_t*") | ||||
|         spu_attr = self.ffi.new("subp_attr_t*") | ||||
|         dvdnav = self.ffi.new("dvdnav_t**", self.ffi.cast("dvdnav_t*", 0)) | ||||
|         self.__check_error(self.lib.dvdnav_open(dvdnav, bytes(path, "utf8"))) | ||||
|         self.dvd = dvdnav[0] | ||||
|         self.__check_error(self.lib.dvdnav_set_readahead_flag(self.dvd, 1)) | ||||
|         self.__get_info() | ||||
|         for title in self.titles: | ||||
|             self.__check_error(self.lib.dvdnav_title_play(self.dvd, title)) | ||||
|             self.titles[title]["audio"] = {} | ||||
|             self.titles[title]["subtitles"] = {} | ||||
|             for n in range(255): | ||||
|                 stream_id = self.lib.dvdnav_get_audio_logical_stream(self.dvd, n) | ||||
|                 if stream_id == -1: | ||||
|                     continue | ||||
|                 self.__check_error( | ||||
|                     self.lib.dvdnav_get_audio_attr(self.dvd, stream_id, audio_attrs) | ||||
|                 ) | ||||
|                 alang = None | ||||
|                 if audio_attrs.lang_type: | ||||
|                     alang = str(audio_attrs.lang_code.to_bytes(2, "big"), "utf8") | ||||
|                 channels = audio_attrs.channels + 1 | ||||
|                 codec = {0: "ac3", 2: "mpeg1", 3: "mpeg-2ext", 4: "lpcm", 6: "dts"}[ | ||||
|                     audio_attrs.audio_format | ||||
|                 ] | ||||
|                 audio_type = { | ||||
|                     0: None, | ||||
|                     1: "normal", | ||||
|                     2: "descriptive", | ||||
|                     3: "director's commentary", | ||||
|                     4: "alternate director's commentary", | ||||
|                 }[audio_attrs.code_extension] | ||||
|                 self.titles[title]["audio"][n] = { | ||||
|                     "stream_id": stream_id, | ||||
|                     "lang": alang, | ||||
|                     "channels": channels, | ||||
|                     "codec": codec, | ||||
|                     "type": audio_type, | ||||
|                 } | ||||
|             for n in range(255): | ||||
|                 stream_id = self.lib.dvdnav_get_spu_logical_stream(self.dvd, n) | ||||
|                 if stream_id == -1: | ||||
|                     continue | ||||
|                 self.__check_error( | ||||
|                     self.lib.dvdnav_get_spu_attr(self.dvd, stream_id, spu_attr) | ||||
|                 ) | ||||
|                 slang = None | ||||
|                 if spu_attr.type == 1: | ||||
|                     slang = str(spu_attr.lang_code.to_bytes(2, "big"), "utf8") | ||||
|                 self.titles[title]["subtitles"][n] = { | ||||
|                     "stream_id": stream_id, | ||||
|                     "lang": slang, | ||||
|                 } | ||||
|         self.__check_error(self.lib.dvdnav_stop(self.dvd)) | ||||
| import functools | ||||
| import os | ||||
| from datetime import timedelta | ||||
| 
 | ||||
| import cffi | ||||
| from tqdm import tqdm | ||||
| 
 | ||||
| from dvdread import DVDRead | ||||
| 
 | ||||
| 
 | ||||
| def loadlib(dll_path, *includes, **kwargs): | ||||
|     ffi = cffi.FFI() | ||||
|     for include in includes: | ||||
|         ffi.cdef(open(include).read(), kwargs) | ||||
|     return ffi, ffi.dlopen(dll_path) | ||||
| 
 | ||||
| 
 | ||||
| domains = { | ||||
|     0: "None", | ||||
|     1: "FirstPlay", | ||||
|     2: "VTSTitle", | ||||
|     4: "VMGM", | ||||
|     8: "VTSMenu", | ||||
| } | ||||
| events = { | ||||
|     0: "DVDNAV_BLOCK_OK", | ||||
|     1: "DVDNAV_NOP", | ||||
|     2: "DVDNAV_STILL_FRAME", | ||||
|     3: "DVDNAV_SPU_STREAM_CHANGE", | ||||
|     4: "DVDNAV_AUDIO_STREAM_CHANGE", | ||||
|     5: "DVDNAV_VTS_CHANGE", | ||||
|     6: "DVDNAV_CELL_CHANGE", | ||||
|     7: "DVDNAV_NAV_PACKET", | ||||
|     8: "DVDNAV_STOP", | ||||
|     9: "DVDNAV_HIGHLIGHT", | ||||
|     10: "DVDNAV_SPU_CLUT_CHANGE", | ||||
|     12: "DVDNAV_HOP_CHANNEL", | ||||
|     13: "DVDNAV_WAIT", | ||||
| } | ||||
| 
 | ||||
| class DVDError(Exception): | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| class DVDNav(object): | ||||
|     def __init__(self, path, verbose=None, method="disc"): | ||||
|         if verbose is None: | ||||
|             os.environ.pop("DVDCSS_VERBOSE", None) | ||||
|         else: | ||||
|             os.environ["DVDCSS_VERBOSE"] = str(verbose) | ||||
|         os.environ["DVDCSS_METHOD"] = method | ||||
|         self.dvd = None | ||||
|         self.ffi, self.lib = loadlib( | ||||
|             "libdvdnav-4.dll", | ||||
|             "dvd_types.h", | ||||
|             "dvd_reader.h", | ||||
|             "ifo_types.h", | ||||
|             "nav_types.h", | ||||
|             "dvdnav_events.h", | ||||
|             "dvdnav.h", | ||||
|             pack=True, | ||||
|         ) | ||||
|         self.path = path | ||||
|         self.titles = {} | ||||
|         self.open(path) | ||||
| 
 | ||||
|     def __del__(self): | ||||
|         self.__check_error(self.lib.dvdnav_close(self.dvd)) | ||||
|         self.dvd = None | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return "<DVD Path={0.path} Title={0.title} Serial={0.serial}".format(self) | ||||
| 
 | ||||
|     def get_blocks(self, title, angle=1, slang=None): | ||||
|         self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1)) | ||||
|         self.__check_error(self.lib.dvdnav_title_play(self.dvd, title)) | ||||
|         curr_angle = self.ffi.new("int32_t*", 0) | ||||
|         num_angles = self.ffi.new("int32_t*", 0) | ||||
|         self.__check_error( | ||||
|             self.lib.dvdnav_get_angle_info(self.dvd, curr_angle, num_angles) | ||||
|         ) | ||||
|         if angle != 0: | ||||
|             if angle < 1 or angle > num_angles[0]: | ||||
|                 raise DVDError("Invalid angle specified!") | ||||
|             if angle != curr_angle[0]: | ||||
|                 self.__check_error(self.lib.dvdnav_angle_change(self.dvd, angle)) | ||||
|         if slang is not None: | ||||
|             self.__check_error(self.lib.dvdnav_spu_language_select(self.dvd, slang)) | ||||
|         event = self.lib.DVDNAV_NOP | ||||
|         buf = self.ffi.new("char[]", 4096) | ||||
|         ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP) | ||||
|         size = self.ffi.new("int32_t*", 0) | ||||
|         pos = self.ffi.new("uint32_t*", 0) | ||||
|         total_size = self.ffi.new("uint32_t*", 0) | ||||
|         progbar = tqdm( | ||||
|             unit_divisor=1024, | ||||
|             unit_scale=True, | ||||
|             unit="iB", | ||||
|             desc="Ripping DVD", | ||||
|             disable=False, | ||||
|         ) | ||||
|         ripped = set() | ||||
|         cells = set() | ||||
|         current_vts = (None,None) | ||||
|         current_cell = None | ||||
|         current_pg = None | ||||
|         while True: | ||||
|             self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size)) | ||||
|             if ( | ||||
|                 self.lib.dvdnav_get_position(self.dvd, pos, total_size) | ||||
|                 == self.lib.DVDNAV_STATUS_OK | ||||
|             ): | ||||
|                 progbar.total = total_size[0] * 2048 | ||||
|                 progbar.n = max(progbar.n, min(progbar.total, pos[0] * 2048)) | ||||
|                 progbar.update(0) | ||||
|                 progbar.set_postfix( | ||||
|                     vts=current_vts, | ||||
|                     cell=current_cell, | ||||
|                     pg=current_pg, | ||||
|                     angle=angle, | ||||
|                     title=title, | ||||
|                 ) | ||||
|             # print("Got event:",events.get(ev[0],ev[0]),size[0]) | ||||
|             if ev[0] in [ | ||||
|                 self.lib.DVDNAV_SPU_CLUT_CHANGE, | ||||
|                 self.lib.DVDNAV_HOP_CHANNEL, | ||||
|                 self.lib.DVDNAV_NOP, | ||||
|                 self.lib.DVDNAV_HIGHLIGHT, | ||||
|             ]: | ||||
|                 continue | ||||
|             elif ev[0] == self.lib.DVDNAV_BLOCK_OK: | ||||
|                 yield self.ffi.buffer(buf, size[0])[:] | ||||
|             elif ev[0] == self.lib.DVDNAV_STOP: | ||||
|                 progbar.write(f"[{title}|{angle}] Stop") | ||||
|                 break | ||||
|             elif ev[0] == self.lib.DVDNAV_NAV_PACKET: | ||||
|                 pass | ||||
|             elif ev[0] == self.lib.DVDNAV_STILL_FRAME: | ||||
|                 self.__check_error(self.lib.dvdnav_still_skip(self.dvd)) | ||||
|             elif ev[0] == self.lib.DVDNAV_WAIT: | ||||
|                 self.__check_error(self.lib.dvdnav_wait_skip(self.dvd)) | ||||
|             elif ev[0] == self.lib.DVDNAV_SPU_STREAM_CHANGE: | ||||
|                 pass | ||||
|             elif ev[0] == self.lib.DVDNAV_AUDIO_STREAM_CHANGE: | ||||
|                 audio = self.ffi.cast("dvdnav_audio_stream_change_event_t*", buf) | ||||
|             elif ev[0] == self.lib.DVDNAV_CELL_CHANGE: | ||||
|                 cell = self.ffi.cast("dvdnav_cell_change_event_t*", buf) | ||||
|                 current_cell = cell.cellN | ||||
|                 current_pg = cell.pgN | ||||
|                 progbar.write( | ||||
|                     f"[{title}|{angle}] Cell: {cell.cellN} ({hex(cell.cell_start)}-{hex(cell.cell_start+cell.cell_length)}), PG: {cell.pgN} ({hex(cell.pg_start)}-{hex(cell.pg_start+cell.pg_length)})" | ||||
|                 ) | ||||
|                 fp=(current_vts[0],current_vts[1],cell.cellN,cell.pgN,cell.cell_length,cell.pg_length,cell.pgc_length,cell.cell_start,cell.pg_start) | ||||
|                 if fp in cells: | ||||
|                     progbar.write(f"[{title}|{angle}] Cells Looped!") | ||||
|                     break | ||||
|                 cells.add(fp) | ||||
|             elif ev[0] == self.lib.DVDNAV_VTS_CHANGE: | ||||
|                 vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf) | ||||
|                 old_domain = domains[vts.old_domain] | ||||
|                 new_domain = domains[vts.new_domain] | ||||
|                 new_vts = (vts.new_vtsN, vts.new_domain) | ||||
|                 old_vts = (vts.old_vtsN, vts.old_domain) | ||||
|                 ripped.add((vts.old_vtsN, vts.old_domain)) | ||||
|                 cells.clear() | ||||
|                 progbar.write(f"[{title}|{angle}] VTS: {vts.old_vtsN} ({vts.old_domain} {old_domain}) -> {vts.new_vtsN} ({vts.new_domain} {new_domain})") | ||||
|                 if (new_vts in ripped) or new_vts==old_vts:  # looped | ||||
|                     progbar.write(f"[{title}|{angle}] VTS Looped!") | ||||
|                     break | ||||
|                 current_vts = (vts.new_vtsN, vts.new_domain) | ||||
|                 if vts.new_domain == 8:  # back to menu | ||||
|                     progbar.write(f"[{title}|{angle}] VTS Back to menu!") | ||||
|                     break | ||||
|                 # yield vts.new_vtsN | ||||
|             else: | ||||
|                 progbar.write( | ||||
|                     f"[{title}|{angle}] Unhandled: {events.get(ev[0],ev[0])} {size[0]}" | ||||
|                 ) | ||||
|         self.__check_error(self.lib.dvdnav_stop(self.dvd)) | ||||
|         progbar.close() | ||||
| 
 | ||||
|     def __check_error(self, ret): | ||||
|         if ret == self.lib.DVDNAV_STATUS_ERR: | ||||
|             if self.dvd: | ||||
|                 err = self.ffi.string(self.lib.dvdnav_err_to_string(self.dvd)) | ||||
|                 raise DVDError(err) | ||||
|             raise DVDError("Unknown error") | ||||
| 
 | ||||
|     def __get_vts(self,title): | ||||
|         buf = self.ffi.new("char[]", 4096) | ||||
|         ev = self.ffi.new("int32_t*", self.lib.DVDNAV_NOP) | ||||
|         size = self.ffi.new("int32_t*", 0) | ||||
|         pos = self.ffi.new("uint32_t*", 0) | ||||
|         total_size = self.ffi.new("uint32_t*", 0) | ||||
|         self.__check_error(self.lib.dvdnav_set_PGC_positioning_flag(self.dvd, 1)) | ||||
|         self.__check_error(self.lib.dvdnav_title_play(self.dvd, title)) | ||||
|         seq=[] | ||||
|         while True: | ||||
|             self.__check_error(self.lib.dvdnav_get_next_block(self.dvd, buf, ev, size)) | ||||
|             if ev[0] == self.lib.DVDNAV_BLOCK_OK: | ||||
|                 self.__check_error(self.lib.dvdnav_get_position(self.dvd, pos, total_size)) | ||||
|                 # print(title,pos[0],total_size[0]) | ||||
|                 if self.lib.dvdnav_next_pg_search(self.dvd)==0: | ||||
|                     break | ||||
|             elif ev[0] == self.lib.DVDNAV_STOP: | ||||
|                 break | ||||
|             elif ev[0] == self.lib.DVDNAV_STILL_FRAME: | ||||
|                 self.__check_error(self.lib.dvdnav_still_skip(self.dvd)) | ||||
|             elif ev[0] == self.lib.DVDNAV_WAIT: | ||||
|                 self.__check_error(self.lib.dvdnav_wait_skip(self.dvd)) | ||||
|             elif ev[0] == self.lib.DVDNAV_VTS_CHANGE: | ||||
|                 vts = self.ffi.cast("dvdnav_vts_change_event_t*", buf) | ||||
|                 old_domain = domains[vts.old_domain] | ||||
|                 new_domain = domains[vts.new_domain] | ||||
|                 seq.append( | ||||
|                     (vts.new_vtsN, new_domain) | ||||
|                 ) | ||||
|                 if vts.new_domain==8: | ||||
|                     break | ||||
|                 continue | ||||
|             # print(title,ev[0],size[0]) | ||||
|         self.__check_error(self.lib.dvdnav_stop(self.dvd)) | ||||
|         # print(title,seq) | ||||
|         return seq | ||||
|         # self.__check_error(self.lib.dvdnav_next_pg_search(self.dvd)) | ||||
| 
 | ||||
|     def __get_titles(self): | ||||
|         titles = self.ffi.new("int32_t*", 0) | ||||
|         p_times = self.ffi.new("uint64_t[]", 512) | ||||
|         times = self.ffi.new("uint64_t**", p_times) | ||||
|         duration = self.ffi.new("uint64_t*", 0) | ||||
|         titles = self.ffi.new("int32_t*", 0) | ||||
|         self.lib.dvdnav_get_number_of_titles(self.dvd, titles) | ||||
|         num_titles = titles[0] | ||||
|         for title in range(0, num_titles + 1): | ||||
|             if self.lib.dvdnav_get_number_of_parts(self.dvd, title, titles) == 0: | ||||
|                 continue | ||||
|             num_parts = titles[0] | ||||
|             self.lib.dvdnav_get_number_of_angles(self.dvd, title, titles) | ||||
|             num_angles = titles[0] | ||||
|             num_chapters = self.lib.dvdnav_describe_title_chapters( | ||||
|                 self.dvd, title, times, duration | ||||
|             ) | ||||
|             if duration[0] == 0: | ||||
|                 continue | ||||
|             chapters = [] | ||||
|             if num_chapters==0 and times[0]==self.ffi.NULL: | ||||
|                 chapters=None | ||||
|             for t in range(num_chapters): | ||||
|                 chapters.append(timedelta(seconds=times[0][t] / 90000)) | ||||
|             self.titles[title] = { | ||||
|                 "parts": num_parts, | ||||
|                 "angles": num_angles, | ||||
|                 "duration": timedelta(seconds=duration[0] / 90000), | ||||
|                 "chapters": chapters, | ||||
|             } | ||||
| 
 | ||||
|     def __get_info(self): | ||||
|         s = self.ffi.new("char**", self.ffi.NULL) | ||||
|         self.lib.dvdnav_get_title_string(self.dvd, s) | ||||
|         self.title = str(self.ffi.string(s[0]), "utf8").strip() or None | ||||
|         self.lib.dvdnav_get_serial_string(self.dvd, s) | ||||
|         self.serial = str(self.ffi.string(s[0]), "utf8").strip() or None | ||||
|         self.__get_titles() | ||||
| 
 | ||||
|     def open(self, path): | ||||
|         audio_attrs = self.ffi.new("audio_attr_t*") | ||||
|         spu_attr = self.ffi.new("subp_attr_t*") | ||||
|         dvdnav = self.ffi.new("dvdnav_t**", self.ffi.cast("dvdnav_t*", 0)) | ||||
|         self.__check_error(self.lib.dvdnav_open(dvdnav, bytes(path, "utf8"))) | ||||
|         self.dvd = dvdnav[0] | ||||
|         self.__check_error(self.lib.dvdnav_set_readahead_flag(self.dvd, 1)) | ||||
|         self.__get_info() | ||||
|         for title in self.titles: | ||||
|             self.__check_error(self.lib.dvdnav_title_play(self.dvd, title)) | ||||
|             self.titles[title]["audio"] = {} | ||||
|             self.titles[title]["subtitles"] = {} | ||||
|             # self.titles[title]["vts"] = self.__get_vts(title) | ||||
|             for n in range(255): | ||||
|                 stream_id = self.lib.dvdnav_get_audio_logical_stream(self.dvd, n) | ||||
|                 if stream_id == -1: | ||||
|                     continue | ||||
|                 self.__check_error( | ||||
|                     self.lib.dvdnav_get_audio_attr(self.dvd, stream_id, audio_attrs) | ||||
|                 ) | ||||
|                 alang = None | ||||
|                 if audio_attrs.lang_type: | ||||
|                     alang = str(audio_attrs.lang_code.to_bytes(2, "big"), "utf8") | ||||
|                 channels = audio_attrs.channels + 1 | ||||
|                 codec = {0: "ac3", 2: "mpeg1", 3: "mpeg-2ext", 4: "lpcm", 6: "dts"}[ | ||||
|                     audio_attrs.audio_format | ||||
|                 ] | ||||
|                 audio_type = { | ||||
|                     0: None, | ||||
|                     1: "normal", | ||||
|                     2: "descriptive", | ||||
|                     3: "director's commentary", | ||||
|                     4: "alternate director's commentary", | ||||
|                 }[audio_attrs.code_extension] | ||||
|                 self.titles[title]["audio"][stream_id] = { | ||||
|                     "lang": alang, | ||||
|                     "channels": channels, | ||||
|                     "codec": codec, | ||||
|                     "type": audio_type, | ||||
|                 } | ||||
|             for n in range(255): | ||||
|                 stream_id = self.lib.dvdnav_get_spu_logical_stream(self.dvd, n) | ||||
|                 if stream_id == -1: | ||||
|                     continue | ||||
|                 self.__check_error( | ||||
|                     self.lib.dvdnav_get_spu_attr(self.dvd, stream_id, spu_attr) | ||||
|                 ) | ||||
|                 slang = None | ||||
|                 if spu_attr.type == 1: | ||||
|                     slang = str(spu_attr.lang_code.to_bytes(2, "big"), "utf8") | ||||
|                 self.titles[title]["subtitles"][stream_id] = { | ||||
|                     "lang": slang, | ||||
|                 } | ||||
|         self.__check_error(self.lib.dvdnav_stop(self.dvd)) | ||||
|         # exit("DEBUG!") | ||||
|  |  | |||
							
								
								
									
										332
									
								
								dvdread.py
									
										
									
									
									
								
							
							
						
						
									
										332
									
								
								dvdread.py
									
										
									
									
									
								
							|  | @ -1,165 +1,167 @@ | |||
| import cffi | ||||
| import os | ||||
| import functools | ||||
| import binascii | ||||
| from datetime import timedelta | ||||
| 
 | ||||
| 
 | ||||
| def loadlib(dll_path, *includes, **kwargs): | ||||
|     ffi = cffi.FFI() | ||||
|     for include in includes: | ||||
|         ffi.cdef(open(include).read(), **kwargs) | ||||
|     return ffi, ffi.dlopen(dll_path) | ||||
| 
 | ||||
| 
 | ||||
| class DVDRead(object): | ||||
|     def __init__(self, path, verbose="0", method="disc"): | ||||
|         if verbose is None: | ||||
|             os.environ.pop("DVDCSS_VERBOSE", None) | ||||
|         else: | ||||
|             os.environ["DVDCSS_VERBOSE"] = str(verbose) | ||||
|         os.environ["DVDCSS_METHOD"] = method | ||||
|         self.dvd = None | ||||
|         self.ffi, self.lib = loadlib( | ||||
|             "libdvdread-8.dll", | ||||
|             "dvd_types.h", | ||||
|             "dvd_reader.h", | ||||
|             "ifo_types.h", | ||||
|             "ifo_read.h", | ||||
|             "ifo_print.h", | ||||
|             "nav_types.h", | ||||
|             "nav_read.h", | ||||
|             "nav_print.h", | ||||
|             packed=True, | ||||
|         ) | ||||
|         self.path = path | ||||
|         self.titles = {} | ||||
|         self.open(path) | ||||
|         self.lb_len=self.lib.DVD_VIDEO_LB_LEN | ||||
| 
 | ||||
|     def grab_ifo(self,title,bup=False): | ||||
|         from tqdm import tqdm | ||||
|         buf = self.ffi.new("unsigned char[]", 512) | ||||
|         if bup: | ||||
|             fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_BACKUP_FILE) | ||||
|         else: | ||||
|             fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_FILE) | ||||
|         total_size = self.lib.DVDFileSize(fh)*self.lb_len | ||||
|         remaining = total_size | ||||
|         num_read = True | ||||
|         pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) | ||||
|         while num_read: | ||||
|             num_read=self.lib.DVDReadBytes( fh, buf, 512) | ||||
|             num_read=min(num_read,remaining) | ||||
|             remaining-=num_read | ||||
|             pbar.update(num_read) | ||||
|             yield self.ffi.buffer(buf,num_read)[:] | ||||
|         self.lib.DVDCloseFile(fh) | ||||
| 
 | ||||
|     def grab_ifos(self): | ||||
|         vmg_ifo = self.lib.ifoOpen(self.dvd, 0) | ||||
|         if vmg_ifo == self.ffi.NULL: | ||||
|             return | ||||
|         title_sets = vmg_ifo.vts_atrt.nr_of_vtss | ||||
|         for t in range(1,title_sets + 1): | ||||
|             vts = self.lib.ifoOpen(self.dvd, t) | ||||
|             if vts == self.ffi.NULL: | ||||
|                 continue | ||||
|             self.lib.ifoClose(vts) | ||||
|             outfile=os.path.join("RIP",f"VTS_{t:02}_0.ifo") | ||||
|             with open(outfile, "wb") as out_ifo: | ||||
|                 for block in self.grab_ifo(t,bup=False): | ||||
|                     out_ifo.write(block) | ||||
|             outfile=os.path.join("RIP",f"VTS_{t:02}_0.bup") | ||||
|             with open(outfile, "wb") as out_ifo: | ||||
|                 for block in self.grab_ifo(t,bup=True): | ||||
|                     out_ifo.write(block) | ||||
|         self.lib.ifoClose(vmg_ifo) | ||||
| 
 | ||||
|     def grab_vob(self,title): | ||||
|         from tqdm import tqdm | ||||
|         buf = self.ffi.new("unsigned char[]", 512 * self.lb_len) | ||||
|         fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_TITLE_VOBS) | ||||
|         total_size = self.lib.DVDFileSize(fh)*self.lb_len | ||||
|         remaining = total_size | ||||
|         num_read = True | ||||
|         pos=0 | ||||
|         pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) | ||||
|         while remaining: | ||||
|             num_read=self.lib.DVDReadBlocks( fh,pos, 512, buf) | ||||
|             if num_read<0: | ||||
|                 raise RuntimeError("Error reading!") | ||||
|             num_read_bytes=num_read*self.lb_len | ||||
|             num_read_bytes=min(num_read_bytes,remaining) | ||||
|             remaining-=num_read_bytes | ||||
|             pbar.update(num_read_bytes) | ||||
|             yield self.ffi.buffer(buf,num_read_bytes)[:] | ||||
|             pos+=num_read | ||||
|         pbar.close() | ||||
|         self.lib.DVDCloseFile(fh) | ||||
| 
 | ||||
|     def grab_vobs(self): | ||||
|         vmg_ifo = self.lib.ifoOpen(self.dvd, 0) | ||||
|         if vmg_ifo == self.ffi.NULL: | ||||
|             return | ||||
|         title_sets = vmg_ifo.vts_atrt.nr_of_vtss | ||||
|         for t in range(1,title_sets + 1): | ||||
|             vts = self.lib.ifoOpen(self.dvd, t) | ||||
|             if vts == self.ffi.NULL: | ||||
|                 continue | ||||
|             self.lib.ifoClose(vts) | ||||
|             outfile=os.path.join("RIP",f"VTS_{t:02}_0.vob") | ||||
|             with open(outfile, "wb") as out_ifo: | ||||
|                 for block in self.grab_vob(t): | ||||
|                     out_ifo.write(block) | ||||
|         self.lib.ifoClose(vmg_ifo) | ||||
| 
 | ||||
| 
 | ||||
|     def test(self): | ||||
|         from tqdm import tqdm | ||||
|         fn = 0 | ||||
|         chunk_size = 2048 | ||||
|         buf = self.ffi.new("unsigned char[]", chunk_size * 2048) | ||||
|         for fn in range(title_sets + 1): | ||||
|             pos = 0 | ||||
|             fh = self.lib.DVDOpenFile(self.dvd, fn, self.lib.DVD_READ_TITLE_VOBS) | ||||
|             if fh: | ||||
|                 total_size = self.lib.DVDFileSize(fh) | ||||
|                 if total_size == -1: | ||||
|                     self.lib.DVDCloseFile(fh) | ||||
|                     break | ||||
|                 pbar = tqdm(total=total_size * 2048, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) | ||||
|                 last=False | ||||
|                 with open(f"out_{fn}.vob", "wb") as out_vob: | ||||
|                     while True: | ||||
|                         if (pos+chunk_size)>total_size: | ||||
|                             chunk_size=total_size-pos | ||||
|                         count = self.lib.DVDReadBlocks(fh, pos, chunk_size, buf) | ||||
|                         if count == -1: | ||||
|                             break | ||||
|                         pbar.update( | ||||
|                             out_vob.write(self.ffi.buffer(buf, count * 2048)[:]) | ||||
|                         ) | ||||
|                         pos += count | ||||
|                         if pos>=total_size: | ||||
|                             break | ||||
|                 self.lib.DVDCloseFile(fh) | ||||
|             fn += 1 | ||||
|             if fn>200: | ||||
|                 break | ||||
| 
 | ||||
|     def __del__(self): | ||||
|         if self.dvd: | ||||
|             self.lib.DVDClose(self.dvd) | ||||
| 
 | ||||
|     def open(self, path): | ||||
|         # self.dvd_css=self.css_lib.dvdcss_open() | ||||
|         self.dvd = self.lib.DVDOpen(bytes(path, "utf8")) | ||||
|         vol_id = self.ffi.new("unsigned char[]", 32) | ||||
|         self.lib.DVDDiscID(self.dvd, vol_id) | ||||
|         self.disc_id = str(binascii.hexlify(self.ffi.buffer(vol_id, 16)[:]), "utf8") | ||||
|         self.lib.DVDUDFVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0) | ||||
|         self.udf_disc_name = str(self.ffi.string(vol_id), "utf8") | ||||
|         self.lib.DVDISOVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0) | ||||
|         self.iso_disc_name = str(self.ffi.string(vol_id), "utf8") | ||||
|         self.ffi.release(vol_id) | ||||
| import binascii | ||||
| import functools | ||||
| import os | ||||
| from datetime import timedelta | ||||
| 
 | ||||
| import cffi | ||||
| 
 | ||||
| 
 | ||||
| def loadlib(dll_path, *includes, **kwargs): | ||||
|     ffi = cffi.FFI() | ||||
|     for include in includes: | ||||
|         ffi.cdef(open(include).read(), **kwargs) | ||||
|     return ffi, ffi.dlopen(dll_path) | ||||
| 
 | ||||
| 
 | ||||
| class DVDRead(object): | ||||
|     def __init__(self, path, verbose="0", method="disc"): | ||||
|         if verbose is None: | ||||
|             os.environ.pop("DVDCSS_VERBOSE", None) | ||||
|         else: | ||||
|             os.environ["DVDCSS_VERBOSE"] = str(verbose) | ||||
|         os.environ["DVDCSS_METHOD"] = method | ||||
|         self.dvd = None | ||||
|         self.ffi, self.lib = loadlib( | ||||
|             "libdvdread-8.dll", | ||||
|             "dvd_types.h", | ||||
|             "dvd_reader.h", | ||||
|             "ifo_types.h", | ||||
|             "ifo_read.h", | ||||
|             "ifo_print.h", | ||||
|             "nav_types.h", | ||||
|             "nav_read.h", | ||||
|             "nav_print.h", | ||||
|             packed=True, | ||||
|         ) | ||||
|         self.path = path | ||||
|         self.titles = {} | ||||
|         self.open(path) | ||||
|         self.lb_len=self.lib.DVD_VIDEO_LB_LEN | ||||
| 
 | ||||
|     def grab_ifo(self,title,bup=False): | ||||
|         from tqdm import tqdm | ||||
|         buf = self.ffi.new("unsigned char[]", 512) | ||||
|         if bup: | ||||
|             fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_BACKUP_FILE) | ||||
|         else: | ||||
|             fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_INFO_FILE) | ||||
|         total_size = self.lib.DVDFileSize(fh)*self.lb_len | ||||
|         remaining = total_size | ||||
|         num_read = True | ||||
|         pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) | ||||
|         while num_read: | ||||
|             num_read=self.lib.DVDReadBytes( fh, buf, 512) | ||||
|             num_read=min(num_read,remaining) | ||||
|             remaining-=num_read | ||||
|             pbar.update(num_read) | ||||
|             yield self.ffi.buffer(buf,num_read)[:] | ||||
|         self.lib.DVDCloseFile(fh) | ||||
|         pbar.close() | ||||
| 
 | ||||
|     def grab_ifos(self): | ||||
|         vmg_ifo = self.lib.ifoOpen(self.dvd, 0) | ||||
|         if vmg_ifo == self.ffi.NULL: | ||||
|             return | ||||
|         title_sets = vmg_ifo.vts_atrt.nr_of_vtss | ||||
|         for t in range(1,title_sets + 1): | ||||
|             vts = self.lib.ifoOpen(self.dvd, t) | ||||
|             if vts == self.ffi.NULL: | ||||
|                 continue | ||||
|             self.lib.ifoClose(vts) | ||||
|             outfile=os.path.join("RIP",f"VTS_{t:02}_0.ifo") | ||||
|             with open(outfile, "wb") as out_ifo: | ||||
|                 for block in self.grab_ifo(t,bup=False): | ||||
|                     out_ifo.write(block) | ||||
|             outfile=os.path.join("RIP",f"VTS_{t:02}_0.bup") | ||||
|             with open(outfile, "wb") as out_ifo: | ||||
|                 for block in self.grab_ifo(t,bup=True): | ||||
|                     out_ifo.write(block) | ||||
|         self.lib.ifoClose(vmg_ifo) | ||||
| 
 | ||||
|     def grab_vob(self,title): | ||||
|         from tqdm import tqdm | ||||
|         buf = self.ffi.new("unsigned char[]", 512 * self.lb_len) | ||||
|         fh = self.lib.DVDOpenFile(self.dvd, title, self.lib.DVD_READ_TITLE_VOBS) | ||||
|         total_size = self.lib.DVDFileSize(fh)*self.lb_len | ||||
|         remaining = total_size | ||||
|         num_read = True | ||||
|         pos=0 | ||||
|         pbar = tqdm(total=total_size, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) | ||||
|         while remaining: | ||||
|             num_read=self.lib.DVDReadBlocks( fh,pos, 512, buf) | ||||
|             if num_read<0: | ||||
|                 raise RuntimeError("Error reading!") | ||||
|             num_read_bytes=num_read*self.lb_len | ||||
|             num_read_bytes=min(num_read_bytes,remaining) | ||||
|             remaining-=num_read_bytes | ||||
|             pbar.update(num_read_bytes) | ||||
|             yield self.ffi.buffer(buf,num_read_bytes)[:] | ||||
|             pos+=num_read | ||||
|         pbar.close() | ||||
|         self.lib.DVDCloseFile(fh) | ||||
| 
 | ||||
|     def grab_vobs(self): | ||||
|         vmg_ifo = self.lib.ifoOpen(self.dvd, 0) | ||||
|         if vmg_ifo == self.ffi.NULL: | ||||
|             return | ||||
|         title_sets = vmg_ifo.vts_atrt.nr_of_vtss | ||||
|         for t in range(1,title_sets + 1): | ||||
|             vts = self.lib.ifoOpen(self.dvd, t) | ||||
|             if vts == self.ffi.NULL: | ||||
|                 continue | ||||
|             self.lib.ifoClose(vts) | ||||
|             outfile=os.path.join("RIP",f"VTS_{t:02}_0.vob") | ||||
|             with open(outfile, "wb") as out_ifo: | ||||
|                 for block in self.grab_vob(t): | ||||
|                     out_ifo.write(block) | ||||
|         self.lib.ifoClose(vmg_ifo) | ||||
| 
 | ||||
| 
 | ||||
|     def test(self): | ||||
|         from tqdm import tqdm | ||||
|         fn = 0 | ||||
|         chunk_size = 2048 | ||||
|         buf = self.ffi.new("unsigned char[]", chunk_size * 2048) | ||||
|         for fn in range(title_sets + 1): | ||||
|             pos = 0 | ||||
|             fh = self.lib.DVDOpenFile(self.dvd, fn, self.lib.DVD_READ_TITLE_VOBS) | ||||
|             if fh: | ||||
|                 total_size = self.lib.DVDFileSize(fh) | ||||
|                 if total_size == -1: | ||||
|                     self.lib.DVDCloseFile(fh) | ||||
|                     break | ||||
|                 pbar = tqdm(total=total_size * 2048, unit="iB", unit_scale=True, unit_divisor=1024,leave=False) | ||||
|                 last=False | ||||
|                 with open(f"out_{fn}.vob", "wb") as out_vob: | ||||
|                     while True: | ||||
|                         if (pos+chunk_size)>total_size: | ||||
|                             chunk_size=total_size-pos | ||||
|                         count = self.lib.DVDReadBlocks(fh, pos, chunk_size, buf) | ||||
|                         if count == -1: | ||||
|                             break | ||||
|                         pbar.update( | ||||
|                             out_vob.write(self.ffi.buffer(buf, count * 2048)[:]) | ||||
|                         ) | ||||
|                         pos += count | ||||
|                         if pos>=total_size: | ||||
|                             break | ||||
|                 self.lib.DVDCloseFile(fh) | ||||
|             fn += 1 | ||||
|             if fn>200: | ||||
|                 break | ||||
| 
 | ||||
|     def __del__(self): | ||||
|         if self.dvd: | ||||
|             self.lib.DVDClose(self.dvd) | ||||
| 
 | ||||
|     def open(self, path): | ||||
|         # self.dvd_css=self.css_lib.dvdcss_open() | ||||
|         self.dvd = self.lib.DVDOpen(bytes(path, "utf8")) | ||||
|         vol_id = self.ffi.new("unsigned char[]", 32) | ||||
|         self.lib.DVDDiscID(self.dvd, vol_id) | ||||
|         self.disc_id = str(binascii.hexlify(self.ffi.buffer(vol_id, 16)[:]), "utf8") | ||||
|         self.lib.DVDUDFVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0) | ||||
|         self.udf_disc_name = str(self.ffi.string(vol_id), "utf8") | ||||
|         self.lib.DVDISOVolumeInfo(self.dvd, vol_id, 32, self.ffi.NULL, 0) | ||||
|         self.iso_disc_name = str(self.ffi.string(vol_id), "utf8") | ||||
|         self.ffi.release(vol_id) | ||||
|  |  | |||
							
								
								
									
										635
									
								
								ff_d2v.py
									
										
									
									
									
								
							
							
						
						
									
										635
									
								
								ff_d2v.py
									
										
									
									
									
								
							|  | @ -1,205 +1,430 @@ | |||
| import sys | ||||
| import json | ||||
| import os | ||||
| import subprocess as SP | ||||
| import itertools as ITT | ||||
| from tqdm import tqdm | ||||
| 
 | ||||
| 
 | ||||
| colorspace = { | ||||
|     "gbr": 0, | ||||
|     "bt709": 1, | ||||
|     "unknown": 2, | ||||
|     "fcc": 4, | ||||
|     "bt470bg": 5, | ||||
|     "smpte170m": 6, | ||||
|     "smpte240m": 7, | ||||
|     "ycgco": 8, | ||||
|     "bt2020nc": 9, | ||||
|     "bt2020c": 10, | ||||
|     "smpte2085": 11, | ||||
|     "chroma-derived-nc": 12, | ||||
|     "chroma-derived-c": 13, | ||||
|     "ictcp": 14, | ||||
| } | ||||
| 
 | ||||
| pict_types = {"I": 0b01, "P": 0b10, "B": 0b11} | ||||
| 
 | ||||
| 
 | ||||
| def make_info(frames): | ||||
|     has_interlaced = any(frame["interlaced_frame"] for frame in frames) | ||||
|     new_gop = "timecode" in frames[0].get("tags", {}) | ||||
|     info = 0x000 | ||||
|     info |= 1 << 11  # always 1 | ||||
|     info |= 0 << 10  # 0=Closed GOP, 1=Open GOP | ||||
|     info |= (not has_interlaced) << 9  # Progressive | ||||
|     info |= new_gop << 8 | ||||
|     return info | ||||
| 
 | ||||
| 
 | ||||
| def make_flags(frames): | ||||
|     flags = [] | ||||
|     for frame in frames: | ||||
|         needs_prev = False | ||||
|         progressive = not int(frame["interlaced_frame"]) | ||||
|         pt = pict_types[frame["pict_type"]] | ||||
|         reserved = 0b00 | ||||
|         tff = int(frame["top_field_first"]) | ||||
|         rff = int(frame["repeat_pict"]) | ||||
|         flag = 0b0 | ||||
|         flag |= (not needs_prev) << 7 | ||||
|         flag |= progressive << 6 | ||||
|         flag |= pt << 4 | ||||
|         flag |= reserved << 2 | ||||
|         flag |= tff << 1 | ||||
|         flag |= rff | ||||
|         flags.append(f"{flag:02x}") | ||||
|     return flags | ||||
| 
 | ||||
| 
 | ||||
| def make_line(frames, stream): | ||||
|     info = f"{make_info(frames):03x}" | ||||
|     matrix = colorspace[stream["color_space"]] | ||||
|     file = 0 | ||||
|     position = frames[0]["pkt_pos"] | ||||
|     skip = 0 | ||||
|     vob = 0 | ||||
|     cell = 0 | ||||
|     flags = make_flags(frames) | ||||
|     return " ".join(map(str, [info, matrix, file, position, skip, vob, cell, *flags])) | ||||
| 
 | ||||
| 
 | ||||
| def get_frames(path): | ||||
|     proc = SP.Popen( | ||||
|         [ | ||||
|             "ffprobe", | ||||
|             "-probesize", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-analyzeduration", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-v", | ||||
|             "fatal", | ||||
|             "-i", | ||||
|             path, | ||||
|             "-select_streams", | ||||
|             "v:0", | ||||
|             "-show_frames", | ||||
|             "-print_format", | ||||
|             "compact", | ||||
|         ], | ||||
|         stdout=SP.PIPE, | ||||
|         stdin=SP.DEVNULL, | ||||
|         bufsize=0, | ||||
|     ) | ||||
|     data = None | ||||
|     for line in proc.stdout: | ||||
|         line = str(line, "utf8").strip().split("|") | ||||
|         line = {line[0]: dict(v.split("=") for v in line[1:])} | ||||
|         yield line | ||||
|     ret = proc.wait() | ||||
|     if ret != 0: | ||||
|         exit(ret) | ||||
|     return data | ||||
| 
 | ||||
| 
 | ||||
| def get_streams(path): | ||||
|     proc = SP.Popen( | ||||
|         [ | ||||
|             "ffprobe", | ||||
|             "-probesize", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-analyzeduration", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-v", | ||||
|             "fatal", | ||||
|             "-i", | ||||
|             path, | ||||
|             "-select_streams", | ||||
|             "v:0", | ||||
|             "-show_streams", | ||||
|             "-show_format", | ||||
|             "-print_format", | ||||
|             "json", | ||||
|         ], | ||||
|         stdout=SP.PIPE, | ||||
|         stdin=SP.DEVNULL, | ||||
|         bufsize=0, | ||||
|     ) | ||||
|     data = json.load(proc.stdout) | ||||
|     ret = proc.wait() | ||||
|     if ret != 0: | ||||
|         exit(ret) | ||||
|     return data["streams"], data["format"] | ||||
| 
 | ||||
| 
 | ||||
| def make_header(file): | ||||
|     return ["DGIndexProjectFile16", "1", os.path.abspath(file)] | ||||
| 
 | ||||
| 
 | ||||
| def make_settings(stream): | ||||
|     pict_size = "x".join(map(str, [stream["width"], stream["height"]])) | ||||
|     frame_rate = list(map(int, stream["r_frame_rate"].split("/"))) | ||||
|     frame_rate = (frame_rate[0] * 1000) // frame_rate[1] | ||||
|     frame_rate = f"{frame_rate} ({stream['r_frame_rate']})" | ||||
|     header = [ | ||||
|         ("Stream_Type", 0),  # Elementary Stream | ||||
|         ("MPEG_Type", 2),  # MPEG-2 | ||||
|         ("iDCT_Algorithm", 5),  # 64-bit IEEE-1180 Reference | ||||
|         ("YUVRGB_Scale", int(stream["color_range"] != "tv")), | ||||
|         ("Luminance_Filter", "0,0"), | ||||
|         ("Clipping", "0,0,0,0"), | ||||
|         ("Aspect_Ratio", stream["display_aspect_ratio"]), | ||||
|         ("Picture_Size", pict_size), | ||||
|         ("Field_Operation", 0),  # Honor Pulldown Flags | ||||
|         ("Frame_Rate", frame_rate), | ||||
|         ("Location", "0,0,0,0"), | ||||
|     ] | ||||
|     for k, v in header: | ||||
|         yield f"{k}={v}" | ||||
| 
 | ||||
| 
 | ||||
| def gen_d2v(path): | ||||
|     yield from make_header(path) | ||||
|     yield "" | ||||
|     streams, fmt = get_streams(path) | ||||
|     stream = [s for s in streams if s["codec_type"] == "video"][0] | ||||
|     stream["index"] = str(stream["index"]) | ||||
|     yield from make_settings(stream) | ||||
|     yield "" | ||||
|     line_buffer = [] | ||||
|     frames = get_frames(path) | ||||
|     prog_bar = tqdm( | ||||
|         frames, | ||||
|         total=int(fmt["size"]), | ||||
|         unit_divisor=1024, | ||||
|         unit_scale=True, | ||||
|         unit="iB", | ||||
|         desc="Writing d2v", | ||||
|     ) | ||||
|     for line in prog_bar: | ||||
|         if "frame" not in line: | ||||
|             continue | ||||
|         frame = line["frame"] | ||||
|         prog_bar.n = min(max(prog_bar.n, int(frame["pkt_pos"])), int(fmt["size"])) | ||||
|         prog_bar.update(0) | ||||
|         if frame["stream_index"] != stream["index"]: | ||||
|             continue | ||||
|         if frame["pict_type"] == "I" and line_buffer: | ||||
|             yield make_line(line_buffer, stream) | ||||
|             line_buffer.clear() | ||||
|         line_buffer.append(frame) | ||||
|     prog_bar.close() | ||||
|     yield None | ||||
| 
 | ||||
| 
 | ||||
| def make_d2v(path): | ||||
|     outfile = os.path.splitext(os.path.basename(path))[0] | ||||
|     outfile = os.path.extsep.join([outfile, "d2v"]) | ||||
|     a, b = ITT.tee(gen_d2v(path)) | ||||
|     next(b) | ||||
|     with open(outfile, "w") as fh: | ||||
|         for line, next_line in zip(a, b): | ||||
|             fh.write(line) | ||||
|             if next_line is None:  # last line, append end marker | ||||
|                 fh.write(" ff") | ||||
|             fh.write("\n") | ||||
| import itertools as ITT | ||||
| import json | ||||
| import os | ||||
| import subprocess as SP | ||||
| import sys | ||||
| from pprint import pprint | ||||
| from fractions import Fraction | ||||
| from glob import glob | ||||
| from collections import Counter | ||||
| from tqdm import tqdm | ||||
| from time import perf_counter | ||||
| 
 | ||||
| 
 | ||||
| def pulldown(fields_per_second, frames_per_second): | ||||
|     f = Fraction(fields_per_second, frames_per_second) | ||||
| 
 | ||||
| 
 | ||||
| colorspace = { | ||||
|     "gbr": 0, | ||||
|     "bt709": 1, | ||||
|     "unknown": 2, | ||||
|     "fcc": 4, | ||||
|     "bt470bg": 5, | ||||
|     "smpte170m": 6, | ||||
|     "smpte240m": 7, | ||||
|     "ycgco": 8, | ||||
|     "bt2020nc": 9, | ||||
|     "bt2020c": 10, | ||||
|     "smpte2085": 11, | ||||
|     "chroma-derived-nc": 12, | ||||
|     "chroma-derived-c": 13, | ||||
|     "ictcp": 14, | ||||
| } | ||||
| 
 | ||||
| pict_types = {"I": 0b01, "P": 0b10, "B": 0b11} | ||||
| 
 | ||||
| 
 | ||||
| def make_info(frames): | ||||
|     has_interlaced = any(frame["interlaced_frame"] for frame in frames) | ||||
|     new_gop = "timecode" in frames[0].get("tags", {}) | ||||
|     info = 0x000 | ||||
|     info |= 1 << 11  # always 1 | ||||
|     info |= 0 << 10  # 0=Closed GOP, 1=Open GOP | ||||
|     info |= (not has_interlaced) << 9  # Progressive | ||||
|     info |= new_gop << 8 | ||||
|     return info | ||||
| 
 | ||||
| 
 | ||||
| def make_flags(frames): | ||||
|     flags = [] | ||||
|     for frame in frames: | ||||
|         needs_prev = False | ||||
|         progressive = not int(frame["interlaced_frame"]) | ||||
|         pt = pict_types[frame["pict_type"]] | ||||
|         reserved = 0b00 | ||||
|         tff = int(frame["top_field_first"]) | ||||
|         rff = int(frame["repeat_pict"]) | ||||
|         flag = 0b0 | ||||
|         flag |= (not needs_prev) << 7 | ||||
|         flag |= progressive << 6 | ||||
|         flag |= pt << 4 | ||||
|         flag |= reserved << 2 | ||||
|         flag |= tff << 1 | ||||
|         flag |= rff | ||||
|         flags.append(f"{flag:02x}") | ||||
|     return flags | ||||
| 
 | ||||
| 
 | ||||
| def make_line(frames, stream): | ||||
|     info = f"{make_info(frames):03x}" | ||||
|     matrix = colorspace[stream.get("color_space", "unknown")] | ||||
|     file = 0 | ||||
|     position = frames[0]["pkt_pos"] | ||||
|     skip = 0 | ||||
|     vob = 0 | ||||
|     cell = 0 | ||||
|     flags = make_flags(frames) | ||||
|     return " ".join(map(str, [info, matrix, file, position, skip, vob, cell, *flags])) | ||||
| 
 | ||||
| 
 | ||||
| def __make_dict(line): | ||||
|     ret = {} | ||||
|     line = line.strip().split("|") | ||||
|     line_type = line[0] | ||||
|     for value in line[1:]: | ||||
|         entry = ret | ||||
|         if "=" not in value: | ||||
|             continue | ||||
|         key_path, value = value.split("=") | ||||
|         key_path = key_path.split(".") | ||||
|         for key in key_path[:-1]: | ||||
|             if ":" in key: | ||||
|                 key = key.split(":")[1] | ||||
|             entry = entry.setdefault(key, {}) | ||||
|         entry[key_path[-1]] = value | ||||
|     return {line_type: ret} | ||||
| 
 | ||||
| 
 | ||||
| def judge(info, num_frames): | ||||
|     threshold = 1  # BFF/TFF threshold value | ||||
|     min_num_frames = 250  # minimal number of frames | ||||
|     idet = info["frame"]["lavfi"]["idet"] | ||||
|     idet_v = {} | ||||
|     for t in "repeated", "single", "multiple": | ||||
|         idet_v[t] = {} | ||||
|         for k, v in idet[t].items(): | ||||
|             try: | ||||
|                 idet_v[t][k] = int(v) | ||||
|             except ValueError: | ||||
|                 try: | ||||
|                     idet_v[t][k] = float(v) | ||||
|                 except ValueError: | ||||
|                     pass | ||||
|     idet = { | ||||
|         "repeat": {k: v for k, v in idet_v["repeated"].items()}, | ||||
|         "single": {k: v for k, v in idet_v["single"].items()}, | ||||
|         "multiple": {k: v for k, v in idet_v["multiple"].items()}, | ||||
|     } | ||||
|     repeat_err = abs( | ||||
|         (idet["repeat"]["neither"] / num_frames) - 0.8 | ||||
|     )  # 2:3 pulldown,4 frames @ ~24 FPS to ~30 FPS = 20% repeated fields | ||||
|     print(f"Derivation from 2:3 Pulldown: {repeat_err:.2%}") | ||||
|     tff = idet["multiple"]["tff"] | ||||
|     bff = idet["multiple"]["bff"] | ||||
|     progressive = idet["multiple"]["progressive"] | ||||
|     interlaced = tff + bff | ||||
|     determined = interlaced + progressive | ||||
|     print(f"Determined: {determined}") | ||||
|     if interlaced: | ||||
|         print(f"Interlaced: {interlaced} (TFF: {tff/interlaced:.2%}, BFF: {bff/interlaced:.2%}) = {interlaced/determined:.2%}") | ||||
|     else: | ||||
|         print(f"Interlaced: {interlaced} = {interlaced/determined:.2%}") | ||||
|     print(f"Progressive: {progressive} = {progressive/determined:.2%}") | ||||
|     if determined == 0: | ||||
|         return idet | ||||
|     idet["num_frames"] = num_frames | ||||
|     idet["interlaced"] = interlaced | ||||
|     idet["progressive"] = progressive | ||||
|     if determined < 50 or determined < min_num_frames: | ||||
|         print("/!\\ Not enough information to determine interlacing type reliably, results may be inacurate /!\\") | ||||
|     if interlaced > progressive: | ||||
|         if tff > bff: | ||||
|             if repeat_err < 1.0: | ||||
|                 idet["vid_type"] = "Telecined TFF" | ||||
|             else: | ||||
|                 idet["vid_type"] = "Interlaced TFF" | ||||
|         elif bff > tff: | ||||
|             if repeat_err < 1.0: | ||||
|                 idet["vid_type"] = "Telecined BFF" | ||||
|             else: | ||||
|                 idet["vid_type"] = "Interlaced BFF" | ||||
|         else: | ||||
|             idet["vid_type"] = "Interlaced?" | ||||
|     else: | ||||
|         idet["vid_type"] = "Progressive" | ||||
|     print(f"Result: {idet['vid_type']}") | ||||
|     return idet | ||||
| 
 | ||||
| 
 | ||||
| def get_meta_interlacing(path): | ||||
|     path = path.replace("\\", "/") | ||||
|     filtergraph = [ | ||||
|         f"movie=\\'{path}\\'", | ||||
|         "cropdetect=limit=0.5:round=2", | ||||
|         "idet", | ||||
|     ] | ||||
|     proc = SP.Popen( | ||||
|         [ | ||||
|             "ffprobe", | ||||
|             "-loglevel", | ||||
|             "fatal", | ||||
|             "-probesize", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-analyzeduration", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-f", | ||||
|             "lavfi", | ||||
|             "-i", | ||||
|             ",".join(filtergraph), | ||||
|             "-select_streams", | ||||
|             "v", | ||||
|             "-show_frames", | ||||
|             "-show_streams", | ||||
|             "-print_format", | ||||
|             "compact", | ||||
|         ], | ||||
|         stdout=SP.PIPE, | ||||
|         stdin=SP.DEVNULL, | ||||
|         bufsize=0, | ||||
|         encoding="utf8", | ||||
|     ) | ||||
|     total_size = int(get_streams(path)[1]["size"]) | ||||
|     data = {} | ||||
|     pbar = tqdm( | ||||
|         total=total_size, | ||||
|         desc="Analyzing video", | ||||
|         unit_divisor=1024, | ||||
|         unit_scale=True, | ||||
|         unit="iB", | ||||
|         leave=False, | ||||
|     ) | ||||
|     frame_num = 0 | ||||
|     from pprint import pformat, pprint | ||||
| 
 | ||||
|     pattern = [] | ||||
|     for line in proc.stdout: | ||||
|         line = __make_dict(line) | ||||
|         data.update(line) | ||||
|         if "frame" in line: | ||||
|             frame_num += 1 | ||||
|             pbar.n = max(pbar.n, min(total_size, int(line["frame"]["pkt_pos"]))) | ||||
|             dt = pbar._time() - pbar.start_t | ||||
|             if dt: | ||||
|                 pbar.set_postfix(frame=frame_num, fps=f"{frame_num / dt:.2f}") | ||||
|             idet = line["frame"].get("lavfi", {}).get("idet") | ||||
|             # rep = idet["repeated"]["current_frame"] | ||||
|             # single = idet["single"]["current_frame"] | ||||
|             # multi = idet["multiple"]["current_frame"] | ||||
|             # pbar.write(repr((rep, single, multi))) | ||||
|             pbar.update(0) | ||||
|     pbar.close() | ||||
|     ret = proc.wait() | ||||
|     if ret != 0: | ||||
|         exit(ret) | ||||
|     stream = data["stream"] | ||||
|     # 30000/1001 | ||||
|     frame_rate = list(map(int, stream["r_frame_rate"].split("/"))) | ||||
|     frame_rate = Fraction(frame_rate[0], frame_rate[1]) | ||||
| 
 | ||||
|     frame_num = int(stream["nb_read_frames"]) | ||||
|     cropdetect = data["frame"]["lavfi"]["cropdetect"] | ||||
|     idet = judge(data, frame_num) | ||||
|     crop = ( | ||||
|         int(cropdetect["x"]), | ||||
|         (int(stream["width"]) - int(cropdetect["w"]) - int(cropdetect["x"])), | ||||
|         int(cropdetect["y"]), | ||||
|         (int(stream["height"]) - int(cropdetect["h"]) - int(cropdetect["y"])), | ||||
|     ) | ||||
|     print(f"Cropping: {crop}") | ||||
|     return {"interlacing":idet, "crop":crop} | ||||
| 
 | ||||
| 
 | ||||
| def get_frames(path): | ||||
|     path = path.replace("\\", "/") | ||||
|     proc = SP.Popen( | ||||
|         [ | ||||
|             "ffprobe", | ||||
|             "-probesize", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-analyzeduration", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-v", | ||||
|             "fatal", | ||||
|             "-i", | ||||
|             path, | ||||
|             "-select_streams", | ||||
|             "v:0", | ||||
|             "-show_frames", | ||||
|             "-print_format", | ||||
|             "compact", | ||||
|         ], | ||||
|         stdout=SP.PIPE, | ||||
|         stdin=SP.DEVNULL, | ||||
|         bufsize=0, | ||||
|         encoding="utf8", | ||||
|     ) | ||||
|     for line in proc.stdout: | ||||
|         yield __make_dict(line) | ||||
|     ret = proc.wait() | ||||
|     if ret != 0: | ||||
|         exit(ret) | ||||
| 
 | ||||
| 
 | ||||
| def get_streams(path): | ||||
|     proc = SP.Popen( | ||||
|         [ | ||||
|             "ffprobe", | ||||
|             "-probesize", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-analyzeduration", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-v", | ||||
|             "fatal", | ||||
|             "-i", | ||||
|             path, | ||||
|             "-select_streams", | ||||
|             "v:0", | ||||
|             "-show_streams", | ||||
|             "-show_format", | ||||
|             "-print_format", | ||||
|             "json", | ||||
|         ], | ||||
|         stdout=SP.PIPE, | ||||
|         stdin=SP.DEVNULL, | ||||
|         bufsize=0, | ||||
|     ) | ||||
|     data = json.load(proc.stdout) | ||||
|     ret = proc.wait() | ||||
|     if ret != 0: | ||||
|         exit(ret) | ||||
|     return data["streams"], data["format"] | ||||
| 
 | ||||
| 
 | ||||
| def make_header(file): | ||||
|     return ["DGIndexProjectFile16", "1", os.path.abspath(file)] | ||||
| 
 | ||||
| 
 | ||||
| def make_settings(stream): | ||||
|     pict_size = "x".join(map(str, [stream["width"], stream["height"]])) | ||||
|     frame_rate = list(map(int, stream["r_frame_rate"].split("/"))) | ||||
|     frame_rate = (frame_rate[0] * 1000) // frame_rate[1] | ||||
|     frame_rate = f"{frame_rate} ({stream['r_frame_rate']})" | ||||
|     header = [ | ||||
|         ("Stream_Type", 0),  # Elementary Stream | ||||
|         ("MPEG_Type", 2),  # MPEG-2 | ||||
|         ("iDCT_Algorithm", 5),  # 64-bit IEEE-1180 Reference | ||||
|         ("YUVRGB_Scale", int(stream["color_range"] != "tv")), | ||||
|         ("Luminance_Filter", "0,0"), | ||||
|         ("Clipping", "0,0,0,0"), | ||||
|         ("Aspect_Ratio", stream["display_aspect_ratio"]), | ||||
|         ("Picture_Size", pict_size), | ||||
|         ("Field_Operation", 0),  # Honor Pulldown Flags | ||||
|         ("Frame_Rate", frame_rate), | ||||
|         ("Location", "0,0,0,0"), | ||||
|     ] | ||||
|     for k, v in header: | ||||
|         yield f"{k}={v}" | ||||
| 
 | ||||
| 
 | ||||
| def gen_d2v(path): | ||||
|     yield from make_header(path) | ||||
|     yield "" | ||||
|     streams, fmt = get_streams(path) | ||||
|     stream = [s for s in streams if s["codec_type"] == "video"][0] | ||||
|     stream["index"] = str(stream["index"]) | ||||
|     yield from make_settings(stream) | ||||
|     yield "" | ||||
|     line_buffer = [] | ||||
|     frames = get_frames(path) | ||||
|     prog_bar = tqdm( | ||||
|         frames, | ||||
|         total=int(fmt["size"]), | ||||
|         unit_divisor=1024, | ||||
|         unit_scale=True, | ||||
|         unit="iB", | ||||
|         desc="Writing d2v", | ||||
|         leave=False, | ||||
|     ) | ||||
|     cropdetect = None | ||||
|     idet = None | ||||
|     frame_num = 0 | ||||
|     t_start=perf_counter() | ||||
|     for line in prog_bar: | ||||
|         if "frame" not in line: | ||||
|             continue | ||||
|         frame = line["frame"] | ||||
|         if frame["stream_index"] != stream["index"]: | ||||
|             continue | ||||
|         prog_bar.n = min(max(prog_bar.n, int(frame["pkt_pos"])), int(fmt["size"])) | ||||
|         fps=frame_num/(perf_counter()-t_start) | ||||
|         prog_bar.set_postfix(frame=frame_num,fps=f"{fps:.02f}") | ||||
|         prog_bar.update(0) | ||||
|         frame_num += 1 | ||||
|         if frame["pict_type"] == "I" and line_buffer: | ||||
|             yield make_line(line_buffer, stream) | ||||
|             line_buffer.clear() | ||||
|         line_buffer.append(frame) | ||||
|     if line_buffer: | ||||
|         yield make_line(line_buffer, stream) | ||||
|     prog_bar.n = int(fmt["size"]) | ||||
|     prog_bar.update(0) | ||||
|     prog_bar.close() | ||||
|     yield None | ||||
| 
 | ||||
| def make_meta(path): | ||||
|     outdir = os.path.dirname(path) | ||||
|     outfile = os.path.splitext(os.path.basename(path))[0] | ||||
|     outfile = os.path.join(outdir, os.path.extsep.join([outfile, "info", "json"])) | ||||
|     if os.path.isfile(outfile): | ||||
|         print(path,"already analyzed, skipping") | ||||
|         return | ||||
|     print("Analyzing", path) | ||||
|     meta = get_meta_interlacing(path) | ||||
|     streams, fmt = get_streams(path) | ||||
|     stream = streams[0] | ||||
|     var = Fraction(int(stream["width"]), int(stream["height"])) | ||||
|     dar = Fraction(*map(int, stream["display_aspect_ratio"].split(":"))) | ||||
|     sar = Fraction(*map(int, stream["sample_aspect_ratio"].split(":"))) | ||||
|     par = sar * dar | ||||
|     meta.update( | ||||
|         { | ||||
|             "par": [par.numerator, par.denominator], | ||||
|             "dar": [dar.numerator, dar.denominator], | ||||
|             "sar": [sar.numerator, sar.denominator], | ||||
|             "var": [var.numerator, var.denominator], | ||||
|         } | ||||
|     ) | ||||
|     print(f"Aspect ratios:") | ||||
|     print(f"    Pixel   {par}") | ||||
|     print(f"    Display {dar}") | ||||
|     print(f"    Screen  {sar}") | ||||
|     print(f"    Video   {var}") | ||||
|     with open(outfile, "w") as fh: | ||||
|         json.dump(meta, fh, indent=4) | ||||
| 
 | ||||
| 
 | ||||
| def make_d2v(path): | ||||
|     outdir = os.path.dirname(path) | ||||
|     outfile = os.path.splitext(os.path.basename(path))[0] | ||||
|     outfile = os.path.join(outdir, os.path.extsep.join([outfile, "d2v"])) | ||||
|     outfile_tmp = os.path.extsep.join([outfile, "tmp"]) | ||||
|     if os.path.isfile(outfile): | ||||
|         print(path,"already indexed, skipping") | ||||
|         return | ||||
|     print("Indexing", path) | ||||
|     a, b = ITT.tee(gen_d2v(path)) | ||||
|     next(b) | ||||
|     with open(outfile_tmp, "w") as fh: | ||||
|         for line, next_line in zip(a, b): | ||||
|             fh.write(line) | ||||
|             if next_line is None:  # last line, append end marker | ||||
|                 fh.write(" ff") | ||||
|             fh.write("\n") | ||||
|     os.rename(outfile_tmp,outfile) | ||||
| 
 | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     for file in ITT.chain.from_iterable(map(glob, sys.argv[1:])): | ||||
|         make_meta(file) | ||||
|         make_d2v(file) | ||||
|  |  | |||
							
								
								
									
										430
									
								
								ifo_read.h
									
										
									
									
									
								
							
							
						
						
									
										430
									
								
								ifo_read.h
									
										
									
									
									
								
							|  | @ -1,215 +1,215 @@ | |||
| /*
 | ||||
|  * Copyright (C) 2000, 2001, 2002 Björn Englund <d4bjorn@dtek.chalmers.se>, | ||||
|  *                                Håkan Hjort <d95hjort@dtek.chalmers.se> | ||||
|  * | ||||
|  * This file is part of libdvdread. | ||||
|  * | ||||
|  * libdvdread 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. | ||||
|  * | ||||
|  * libdvdread 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 libdvdread; if not, write to the Free Software Foundation, Inc., | ||||
|  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * handle = ifoOpen(dvd, title); | ||||
|  * | ||||
|  * Opens an IFO and reads in all the data for the IFO file corresponding to the | ||||
|  * given title.  If title 0 is given, the video manager IFO file is read. | ||||
|  * Returns a handle to a completely parsed structure. | ||||
|  */ | ||||
| ifo_handle_t *ifoOpen(dvd_reader_t *, int ); | ||||
| 
 | ||||
| /**
 | ||||
|  * handle = ifoOpenVMGI(dvd); | ||||
|  * | ||||
|  * Opens an IFO and reads in _only_ the vmgi_mat data.  This call can be used | ||||
|  * together with the calls below to read in each segment of the IFO file on | ||||
|  * demand. | ||||
|  */ | ||||
| ifo_handle_t *ifoOpenVMGI(dvd_reader_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * handle = ifoOpenVTSI(dvd, title); | ||||
|  * | ||||
|  * Opens an IFO and reads in _only_ the vtsi_mat data.  This call can be used | ||||
|  * together with the calls below to read in each segment of the IFO file on | ||||
|  * demand. | ||||
|  */ | ||||
| ifo_handle_t *ifoOpenVTSI(dvd_reader_t *, int); | ||||
| 
 | ||||
| /**
 | ||||
|  * ifoClose(ifofile); | ||||
|  * Cleans up the IFO information.  This will free all data allocated for the | ||||
|  * substructures. | ||||
|  */ | ||||
| void ifoClose(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * The following functions are for reading only part of the VMGI/VTSI files. | ||||
|  * Returns 1 if the data was successfully read and 0 on error. | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_PLT_MAIT(ifofile); | ||||
|  * | ||||
|  * Read in the Parental Management Information table, filling the | ||||
|  * ifofile->ptl_mait structure and its substructures.  This data is only | ||||
|  * located in the video manager information file.  This fills the | ||||
|  * ifofile->ptl_mait structure and all its substructures. | ||||
|  */ | ||||
| int ifoRead_PTL_MAIT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_VTS_ATRT(ifofile); | ||||
|  * | ||||
|  * Read in the attribute table for the main menu vob, filling the | ||||
|  * ifofile->vts_atrt structure and its substructures.  Only located in the | ||||
|  * video manager information file.  This fills in the ifofile->vts_atrt | ||||
|  * structure and all its substructures. | ||||
|  */ | ||||
| int ifoRead_VTS_ATRT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_TT_SRPT(ifofile); | ||||
|  * | ||||
|  * Reads the title info for the main menu, filling the ifofile->tt_srpt | ||||
|  * structure and its substructures.  This data is only located in the video | ||||
|  * manager information file.  This structure is mandatory in the IFO file. | ||||
|  */ | ||||
| int ifoRead_TT_SRPT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_VTS_PTT_SRPT(ifofile); | ||||
|  * | ||||
|  * Reads in the part of title search pointer table, filling the | ||||
|  * ifofile->vts_ptt_srpt structure and its substructures.  This data is only | ||||
|  * located in the video title set information file.  This structure is | ||||
|  * mandatory, and must be included in the VTSI file. | ||||
|  */ | ||||
| int ifoRead_VTS_PTT_SRPT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_FP_PGC(ifofile); | ||||
|  * | ||||
|  * Reads in the first play program chain data, filling the | ||||
|  * ifofile->first_play_pgc structure.  This data is only located in the video | ||||
|  * manager information file (VMGI).  This structure is optional. | ||||
|  */ | ||||
| int ifoRead_FP_PGC(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_PGCIT(ifofile); | ||||
|  * | ||||
|  * Reads in the program chain information table for the video title set.  Fills | ||||
|  * in the ifofile->vts_pgcit structure and its substructures, which includes | ||||
|  * the data for each program chain in the set.  This data is only located in | ||||
|  * the video title set information file.  This structure is mandatory, and must | ||||
|  * be included in the VTSI file. | ||||
|  */ | ||||
| int ifoRead_PGCIT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_PGCI_UT(ifofile); | ||||
|  * | ||||
|  * Reads in the menu PGCI unit table for the menu VOB.  For the video manager, | ||||
|  * this corresponds to the VIDEO_TS.VOB file, and for each title set, this | ||||
|  * corresponds to the VTS_XX_0.VOB file.  This data is located in both the | ||||
|  * video manager and video title set information files.  For VMGI files, this | ||||
|  * fills the ifofile->vmgi_pgci_ut structure and all its substructures.  For | ||||
|  * VTSI files, this fills the ifofile->vtsm_pgci_ut structure. | ||||
|  */ | ||||
| int ifoRead_PGCI_UT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_VTS_TMAPT(ifofile); | ||||
|  * | ||||
|  * Reads in the VTS Time Map Table, this data is only located in the video | ||||
|  * title set information file.  This fills the ifofile->vts_tmapt structure | ||||
|  * and all its substructures.  When pressent enables VOBU level time-based | ||||
|  * seeking for One_Sequential_PGC_Titles. | ||||
|  */ | ||||
| int ifoRead_VTS_TMAPT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_C_ADT(ifofile); | ||||
|  * | ||||
|  * Reads in the cell address table for the menu VOB.  For the video manager, | ||||
|  * this corresponds to the VIDEO_TS.VOB file, and for each title set, this | ||||
|  * corresponds to the VTS_XX_0.VOB file.  This data is located in both the | ||||
|  * video manager and video title set information files.  For VMGI files, this | ||||
|  * fills the ifofile->vmgm_c_adt structure and all its substructures.  For VTSI | ||||
|  * files, this fills the ifofile->vtsm_c_adt structure. | ||||
|  */ | ||||
| int ifoRead_C_ADT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_TITLE_C_ADT(ifofile); | ||||
|  * | ||||
|  * Reads in the cell address table for the video title set corresponding to | ||||
|  * this IFO file.  This data is only located in the video title set information | ||||
|  * file.  This structure is mandatory, and must be included in the VTSI file. | ||||
|  * This call fills the ifofile->vts_c_adt structure and its substructures. | ||||
|  */ | ||||
| int ifoRead_TITLE_C_ADT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_VOBU_ADMAP(ifofile); | ||||
|  * | ||||
|  * Reads in the VOBU address map for the menu VOB.  For the video manager, this | ||||
|  * corresponds to the VIDEO_TS.VOB file, and for each title set, this | ||||
|  * corresponds to the VTS_XX_0.VOB file.  This data is located in both the | ||||
|  * video manager and video title set information files.  For VMGI files, this | ||||
|  * fills the ifofile->vmgm_vobu_admap structure and all its substructures.  For | ||||
|  * VTSI files, this fills the ifofile->vtsm_vobu_admap structure. | ||||
|  */ | ||||
| int ifoRead_VOBU_ADMAP(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_TITLE_VOBU_ADMAP(ifofile); | ||||
|  * | ||||
|  * Reads in the VOBU address map for the associated video title set.  This data | ||||
|  * is only located in the video title set information file.  This structure is | ||||
|  * mandatory, and must be included in the VTSI file.  Fills the | ||||
|  * ifofile->vts_vobu_admap structure and its substructures. | ||||
|  */ | ||||
| int ifoRead_TITLE_VOBU_ADMAP(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_TXTDT_MGI(ifofile); | ||||
|  * | ||||
|  * Reads in the text data strings for the DVD.  Fills the ifofile->txtdt_mgi | ||||
|  * structure and all its substructures.  This data is only located in the video | ||||
|  * manager information file.  This structure is mandatory, and must be included | ||||
|  * in the VMGI file. | ||||
|  */ | ||||
| int ifoRead_TXTDT_MGI(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * The following functions are used for freeing parsed sections of the | ||||
|  * ifo_handle_t structure and the allocated substructures.  The free calls | ||||
|  * below are safe:  they will not mind if you attempt to free part of an IFO | ||||
|  * file which was not read in or which does not exist. | ||||
|  */ | ||||
| void ifoFree_PTL_MAIT(ifo_handle_t *); | ||||
| void ifoFree_VTS_ATRT(ifo_handle_t *); | ||||
| void ifoFree_TT_SRPT(ifo_handle_t *); | ||||
| void ifoFree_VTS_PTT_SRPT(ifo_handle_t *); | ||||
| void ifoFree_FP_PGC(ifo_handle_t *); | ||||
| void ifoFree_PGCIT(ifo_handle_t *); | ||||
| void ifoFree_PGCI_UT(ifo_handle_t *); | ||||
| void ifoFree_VTS_TMAPT(ifo_handle_t *); | ||||
| void ifoFree_C_ADT(ifo_handle_t *); | ||||
| void ifoFree_TITLE_C_ADT(ifo_handle_t *); | ||||
| void ifoFree_VOBU_ADMAP(ifo_handle_t *); | ||||
| void ifoFree_TITLE_VOBU_ADMAP(ifo_handle_t *); | ||||
| void ifoFree_TXTDT_MGI(ifo_handle_t *); | ||||
| 
 | ||||
| /*
 | ||||
|  * Copyright (C) 2000, 2001, 2002 Björn Englund <d4bjorn@dtek.chalmers.se>, | ||||
|  *                                Håkan Hjort <d95hjort@dtek.chalmers.se> | ||||
|  * | ||||
|  * This file is part of libdvdread. | ||||
|  * | ||||
|  * libdvdread 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. | ||||
|  * | ||||
|  * libdvdread 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 libdvdread; if not, write to the Free Software Foundation, Inc., | ||||
|  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * handle = ifoOpen(dvd, title); | ||||
|  * | ||||
|  * Opens an IFO and reads in all the data for the IFO file corresponding to the | ||||
|  * given title.  If title 0 is given, the video manager IFO file is read. | ||||
|  * Returns a handle to a completely parsed structure. | ||||
|  */ | ||||
| ifo_handle_t *ifoOpen(dvd_reader_t *, int ); | ||||
| 
 | ||||
| /**
 | ||||
|  * handle = ifoOpenVMGI(dvd); | ||||
|  * | ||||
|  * Opens an IFO and reads in _only_ the vmgi_mat data.  This call can be used | ||||
|  * together with the calls below to read in each segment of the IFO file on | ||||
|  * demand. | ||||
|  */ | ||||
| ifo_handle_t *ifoOpenVMGI(dvd_reader_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * handle = ifoOpenVTSI(dvd, title); | ||||
|  * | ||||
|  * Opens an IFO and reads in _only_ the vtsi_mat data.  This call can be used | ||||
|  * together with the calls below to read in each segment of the IFO file on | ||||
|  * demand. | ||||
|  */ | ||||
| ifo_handle_t *ifoOpenVTSI(dvd_reader_t *, int); | ||||
| 
 | ||||
| /**
 | ||||
|  * ifoClose(ifofile); | ||||
|  * Cleans up the IFO information.  This will free all data allocated for the | ||||
|  * substructures. | ||||
|  */ | ||||
| void ifoClose(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * The following functions are for reading only part of the VMGI/VTSI files. | ||||
|  * Returns 1 if the data was successfully read and 0 on error. | ||||
|  */ | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_PLT_MAIT(ifofile); | ||||
|  * | ||||
|  * Read in the Parental Management Information table, filling the | ||||
|  * ifofile->ptl_mait structure and its substructures.  This data is only | ||||
|  * located in the video manager information file.  This fills the | ||||
|  * ifofile->ptl_mait structure and all its substructures. | ||||
|  */ | ||||
| int ifoRead_PTL_MAIT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_VTS_ATRT(ifofile); | ||||
|  * | ||||
|  * Read in the attribute table for the main menu vob, filling the | ||||
|  * ifofile->vts_atrt structure and its substructures.  Only located in the | ||||
|  * video manager information file.  This fills in the ifofile->vts_atrt | ||||
|  * structure and all its substructures. | ||||
|  */ | ||||
| int ifoRead_VTS_ATRT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_TT_SRPT(ifofile); | ||||
|  * | ||||
|  * Reads the title info for the main menu, filling the ifofile->tt_srpt | ||||
|  * structure and its substructures.  This data is only located in the video | ||||
|  * manager information file.  This structure is mandatory in the IFO file. | ||||
|  */ | ||||
| int ifoRead_TT_SRPT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_VTS_PTT_SRPT(ifofile); | ||||
|  * | ||||
|  * Reads in the part of title search pointer table, filling the | ||||
|  * ifofile->vts_ptt_srpt structure and its substructures.  This data is only | ||||
|  * located in the video title set information file.  This structure is | ||||
|  * mandatory, and must be included in the VTSI file. | ||||
|  */ | ||||
| int ifoRead_VTS_PTT_SRPT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_FP_PGC(ifofile); | ||||
|  * | ||||
|  * Reads in the first play program chain data, filling the | ||||
|  * ifofile->first_play_pgc structure.  This data is only located in the video | ||||
|  * manager information file (VMGI).  This structure is optional. | ||||
|  */ | ||||
| int ifoRead_FP_PGC(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_PGCIT(ifofile); | ||||
|  * | ||||
|  * Reads in the program chain information table for the video title set.  Fills | ||||
|  * in the ifofile->vts_pgcit structure and its substructures, which includes | ||||
|  * the data for each program chain in the set.  This data is only located in | ||||
|  * the video title set information file.  This structure is mandatory, and must | ||||
|  * be included in the VTSI file. | ||||
|  */ | ||||
| int ifoRead_PGCIT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_PGCI_UT(ifofile); | ||||
|  * | ||||
|  * Reads in the menu PGCI unit table for the menu VOB.  For the video manager, | ||||
|  * this corresponds to the VIDEO_TS.VOB file, and for each title set, this | ||||
|  * corresponds to the VTS_XX_0.VOB file.  This data is located in both the | ||||
|  * video manager and video title set information files.  For VMGI files, this | ||||
|  * fills the ifofile->vmgi_pgci_ut structure and all its substructures.  For | ||||
|  * VTSI files, this fills the ifofile->vtsm_pgci_ut structure. | ||||
|  */ | ||||
| int ifoRead_PGCI_UT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_VTS_TMAPT(ifofile); | ||||
|  * | ||||
|  * Reads in the VTS Time Map Table, this data is only located in the video | ||||
|  * title set information file.  This fills the ifofile->vts_tmapt structure | ||||
|  * and all its substructures.  When pressent enables VOBU level time-based | ||||
|  * seeking for One_Sequential_PGC_Titles. | ||||
|  */ | ||||
| int ifoRead_VTS_TMAPT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_C_ADT(ifofile); | ||||
|  * | ||||
|  * Reads in the cell address table for the menu VOB.  For the video manager, | ||||
|  * this corresponds to the VIDEO_TS.VOB file, and for each title set, this | ||||
|  * corresponds to the VTS_XX_0.VOB file.  This data is located in both the | ||||
|  * video manager and video title set information files.  For VMGI files, this | ||||
|  * fills the ifofile->vmgm_c_adt structure and all its substructures.  For VTSI | ||||
|  * files, this fills the ifofile->vtsm_c_adt structure. | ||||
|  */ | ||||
| int ifoRead_C_ADT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_TITLE_C_ADT(ifofile); | ||||
|  * | ||||
|  * Reads in the cell address table for the video title set corresponding to | ||||
|  * this IFO file.  This data is only located in the video title set information | ||||
|  * file.  This structure is mandatory, and must be included in the VTSI file. | ||||
|  * This call fills the ifofile->vts_c_adt structure and its substructures. | ||||
|  */ | ||||
| int ifoRead_TITLE_C_ADT(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_VOBU_ADMAP(ifofile); | ||||
|  * | ||||
|  * Reads in the VOBU address map for the menu VOB.  For the video manager, this | ||||
|  * corresponds to the VIDEO_TS.VOB file, and for each title set, this | ||||
|  * corresponds to the VTS_XX_0.VOB file.  This data is located in both the | ||||
|  * video manager and video title set information files.  For VMGI files, this | ||||
|  * fills the ifofile->vmgm_vobu_admap structure and all its substructures.  For | ||||
|  * VTSI files, this fills the ifofile->vtsm_vobu_admap structure. | ||||
|  */ | ||||
| int ifoRead_VOBU_ADMAP(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_TITLE_VOBU_ADMAP(ifofile); | ||||
|  * | ||||
|  * Reads in the VOBU address map for the associated video title set.  This data | ||||
|  * is only located in the video title set information file.  This structure is | ||||
|  * mandatory, and must be included in the VTSI file.  Fills the | ||||
|  * ifofile->vts_vobu_admap structure and its substructures. | ||||
|  */ | ||||
| int ifoRead_TITLE_VOBU_ADMAP(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * okay = ifoRead_TXTDT_MGI(ifofile); | ||||
|  * | ||||
|  * Reads in the text data strings for the DVD.  Fills the ifofile->txtdt_mgi | ||||
|  * structure and all its substructures.  This data is only located in the video | ||||
|  * manager information file.  This structure is mandatory, and must be included | ||||
|  * in the VMGI file. | ||||
|  */ | ||||
| int ifoRead_TXTDT_MGI(ifo_handle_t *); | ||||
| 
 | ||||
| /**
 | ||||
|  * The following functions are used for freeing parsed sections of the | ||||
|  * ifo_handle_t structure and the allocated substructures.  The free calls | ||||
|  * below are safe:  they will not mind if you attempt to free part of an IFO | ||||
|  * file which was not read in or which does not exist. | ||||
|  */ | ||||
| void ifoFree_PTL_MAIT(ifo_handle_t *); | ||||
| void ifoFree_VTS_ATRT(ifo_handle_t *); | ||||
| void ifoFree_TT_SRPT(ifo_handle_t *); | ||||
| void ifoFree_VTS_PTT_SRPT(ifo_handle_t *); | ||||
| void ifoFree_FP_PGC(ifo_handle_t *); | ||||
| void ifoFree_PGCIT(ifo_handle_t *); | ||||
| void ifoFree_PGCI_UT(ifo_handle_t *); | ||||
| void ifoFree_VTS_TMAPT(ifo_handle_t *); | ||||
| void ifoFree_C_ADT(ifo_handle_t *); | ||||
| void ifoFree_TITLE_C_ADT(ifo_handle_t *); | ||||
| void ifoFree_VOBU_ADMAP(ifo_handle_t *); | ||||
| void ifoFree_TITLE_VOBU_ADMAP(ifo_handle_t *); | ||||
| void ifoFree_TXTDT_MGI(ifo_handle_t *); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										1366
									
								
								ifo_types.h
									
										
									
									
									
								
							
							
						
						
									
										1366
									
								
								ifo_types.h
									
										
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										194
									
								
								vob_demux.py
									
										
									
									
									
								
							
							
						
						
									
										194
									
								
								vob_demux.py
									
										
									
									
									
								
							|  | @ -1,87 +1,107 @@ | |||
| import sys | ||||
| import os | ||||
| import json | ||||
| import subprocess as SP | ||||
| 
 | ||||
| 
 | ||||
| def get_streams(path): | ||||
|     proc = SP.Popen( | ||||
|         [ | ||||
|             "ffprobe", | ||||
|             "-probesize", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-analyzeduration", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-v", | ||||
|             "fatal", | ||||
|             "-i", | ||||
|             path, | ||||
|             "-show_streams", | ||||
|             "-show_format", | ||||
|             "-print_format", | ||||
|             "json", | ||||
|         ], | ||||
|         stdout=SP.PIPE, | ||||
|         stdin=SP.DEVNULL, | ||||
|         bufsize=0, | ||||
|     ) | ||||
|     data = json.load(proc.stdout) | ||||
|     ret = proc.wait() | ||||
|     if ret != 0: | ||||
|         return [], {} | ||||
|     return data["streams"], data["format"] | ||||
| 
 | ||||
| 
 | ||||
| types = { | ||||
|     "mpeg2video": "m2v", | ||||
|     "ac3": "ac3", | ||||
|     "dvd_subtitle": "sup", | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| def demux(path): | ||||
|     folder = os.path.dirname(path) | ||||
|     basename = os.path.splitext(os.path.basename(path))[0] | ||||
|     streams, fmt = get_streams(path) | ||||
|     cmd = [ | ||||
|         "ffmpeg", | ||||
|         "-y", | ||||
|         # "-fflags","+genpts+igndts", | ||||
|         "-probesize", | ||||
|         str(0x7FFFFFFF), | ||||
|         "-analyzeduration", | ||||
|         str(0x7FFFFFFF), | ||||
|         "-i", | ||||
|         path, | ||||
|         "-strict", | ||||
|         "-2", | ||||
|         "-vcodec", | ||||
|         "copy", | ||||
|         "-acodec", | ||||
|         "copy", | ||||
|         "-scodec", | ||||
|         "copy", | ||||
|     ] | ||||
|     need_ffmpeg = False | ||||
|     for stream in streams: | ||||
|         codec = stream["codec_name"] | ||||
|         ext = types.get(codec, codec) | ||||
|         idx = stream["index"] | ||||
|         hex_id = stream["id"] | ||||
|         codec_name = stream["codec_long_name"] | ||||
|         outfile = os.path.join(folder, f"{basename}_{idx}_{hex_id}") | ||||
|         if codec=="dvd_nav_packet": | ||||
|             continue | ||||
|         print(idx, hex_id, codec_name, codec) | ||||
|         if codec == "dvd_subtitle": | ||||
|             SP.check_call([ | ||||
|                 "mencoder",path,"-vobsuboutindex",str(idx),"-vobsubout", outfile,"-nosound","-ovc", "copy", "-o",os.devnull | ||||
|             ]) | ||||
|             continue | ||||
|         cmd += ["-map", f"0:#{hex_id}", "-strict", "-2", outfile + f".{ext}"] | ||||
|         need_ffmpeg = True | ||||
|     if need_ffmpeg: | ||||
|         SP.check_call(cmd) | ||||
| 
 | ||||
| if __name__=="__main__": | ||||
|     demux(sys.argv[1]) | ||||
| import json | ||||
| import os | ||||
| import subprocess as SP | ||||
| import sys | ||||
| import shutil | ||||
| 
 | ||||
| 
 | ||||
| def get_streams(path): | ||||
|     proc = SP.Popen( | ||||
|         [ | ||||
|             "ffprobe", | ||||
|             "-probesize", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-analyzeduration", | ||||
|             str(0x7FFFFFFF), | ||||
|             "-v", | ||||
|             "fatal", | ||||
|             "-i", | ||||
|             path, | ||||
|             "-show_streams", | ||||
|             "-show_format", | ||||
|             "-print_format", | ||||
|             "json", | ||||
|         ], | ||||
|         stdout=SP.PIPE, | ||||
|         stdin=SP.DEVNULL, | ||||
|         bufsize=0, | ||||
|     ) | ||||
|     data = json.load(proc.stdout) | ||||
|     ret = proc.wait() | ||||
|     if ret != 0: | ||||
|         return [], {} | ||||
|     return data["streams"], data["format"] | ||||
| 
 | ||||
| def ccextract(files): | ||||
|     ccextractor = shutil.which("ccextractor") or shutil.which("ccextractorwinfull") | ||||
|     if ccextractor is None and os.name=="nt": | ||||
|             ccextractor=os.path.expandvars(os.path.join("${PROGRAMFILES(X86)}","CCExtractor","ccextractorwinfull.exe")) | ||||
|     if not os.path.isfile(ccextractor): | ||||
|         print("WARNING: CCExtractor not found") | ||||
|         return [] | ||||
|     new_files=[] | ||||
|     for file in files: | ||||
|         outfile=os.path.splitext(file)[0] | ||||
|         outfile=os.path.extsep.join([outfile, "cc.srt"]) | ||||
|         ret=SP.call([ccextractor, "-sc", "-sbs", "-autodash", "-trim","-nobom","-o", outfile, file]) | ||||
|         if ret==10: | ||||
|             if os.path.isfile(outfile): | ||||
|                 os.unlink(outfile) | ||||
|                 continue | ||||
|         new_files.append(outfile) | ||||
|     return new_files | ||||
| 
 | ||||
| 
 | ||||
| types = {"mpeg2video": "m2v", "ac3": "ac3", "dvd_subtitle": "sub.mkv", "eia_608": "srt"} | ||||
| 
 | ||||
| def demux(path): | ||||
|     folder = os.path.dirname(path) | ||||
|     basename = os.path.splitext(os.path.basename(path))[0] | ||||
|     streams, _ = get_streams(path) | ||||
|     cmd = [ | ||||
|         "ffmpeg", | ||||
|         "-y", | ||||
|         "-probesize", | ||||
|         str(0x7FFFFFFF), | ||||
|         "-analyzeduration", | ||||
|         str(0x7FFFFFFF), | ||||
|         "-i", | ||||
|         path, | ||||
|         "-strict", | ||||
|         "-2", | ||||
|     ] | ||||
|     caption_files = [] | ||||
|     for stream in streams: | ||||
|         codec = stream["codec_name"] | ||||
|         ext = types.get(codec, codec) | ||||
|         hex_id = stream["id"] | ||||
|         codec_name = stream["codec_long_name"] | ||||
|         outfile = os.path.join(folder, f"{basename}_{hex_id}") | ||||
|         if codec == "dvd_nav_packet": | ||||
|             continue | ||||
|         outfile = os.path.extsep.join([outfile, ext]) | ||||
|         print(hex_id, codec_name, codec) | ||||
|         if codec == "mpeg2video": | ||||
|             caption_files.append(outfile) | ||||
|         cmd += [ | ||||
|             "-map", | ||||
|             f"0:#{hex_id}", | ||||
|             "-vcodec", | ||||
|             "copy", | ||||
|             "-acodec", | ||||
|             "copy", | ||||
|             "-scodec", | ||||
|             "copy", | ||||
|             "-strict", | ||||
|             "-2", | ||||
|             outfile, | ||||
|         ] | ||||
|     SP.check_call(cmd) | ||||
|     ccextract(caption_files) | ||||
|     for file in os.listdir(folder): | ||||
|         if os.path.isfile(file): | ||||
|             if os.stat(file).st_size==0: | ||||
|                 os.unlink(file) | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     demux(sys.argv[1]) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue