diff --git a/__pycache__/mediascan.cpython-313.pyc b/__pycache__/mediascan.cpython-313.pyc new file mode 100644 index 0000000..07ac527 --- /dev/null +++ b/__pycache__/mediascan.cpython-313.pyc Binary files differ diff --git a/app.py b/app.py index ed57499..3a332d6 100644 --- a/app.py +++ b/app.py @@ -1,8 +1,7 @@ from flask import Flask, render_template, request, jsonify import os import json -import ffmpeg -import subprocess +from mediascan import list_media_files, filter_media_files, human_readable_size, get_media_info_with_ffprobe app = Flask(__name__) @@ -21,161 +20,34 @@ config = load_config() - -def get_media_info_with_ffprobe(file_path): - """ - Извлекает информацию о видео- и аудиопотоках, а также контейнере. - :param file_path: Путь к файлу - :return: Словарь с информацией о видео, аудио и контейнере - """ - try: - if not os.path.exists(file_path): - print(f"File not found: {file_path}") - return None - - # Команда для потоков - streams_command = [ - "ffprobe", "-v", "error", - "-show_entries", "stream=index,codec_type,codec_name,width,height,bit_rate,channels,channel_layout", - "-of", "json", file_path - ] - - # Команда для контейнера - format_command = [ - "ffprobe", "-v", "error", - "-show_entries", "format=duration,bit_rate,size", - "-of", "json", file_path - ] - - # Выполняем команды - streams_result = subprocess.run(streams_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - format_result = subprocess.run(format_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - - # Проверяем ошибки - if streams_result.returncode != 0: - print(f"ffprobe stream error: {streams_result.stderr}") - return None - if format_result.returncode != 0: - print(f"ffprobe format error: {format_result.stderr}") - return None - - # Парсим выводы - streams_data = json.loads(streams_result.stdout) - format_data = json.loads(format_result.stdout) - - # Извлекаем данные контейнера - format_info = format_data.get("format", {}) - container_duration = float(format_info.get("duration", 0)) - container_bitrate = int(format_info.get("bit_rate", 0)) // 1000 # Перевод в Кбит/с - file_size = int(format_info.get("size", 0)) - - # Обрабатываем потоки - media_info = { - "video": [], - "audio": [], - "container": { - "duration": f"{container_duration:.2f} seconds" if container_duration else "Unknown", - "bitrate": f"{container_bitrate} Kbit/s" if container_bitrate else "Unknown", - "size": f"{file_size / (1024**2):.2f} MB" if file_size else "Unknown" - } - } - - for stream in streams_data.get("streams", []): - codec_type = stream.get("codec_type") - codec_name = stream.get("codec_name") - stream_bitrate = int(stream.get("bit_rate", 0)) // 1000 if stream.get("bit_rate") else None - width = stream.get("width") - height = stream.get("height") - channels = stream.get("channels", "Unknown") - channel_layout = stream.get("channel_layout", "Unknown") - - # Если битрейт потока отсутствует, используем битрейт контейнера - bitrate = stream_bitrate if stream_bitrate else container_bitrate - - if codec_type == "video": - media_info["video"].append({ - "resolution": f"{width}x{height}" if width and height else "Unknown", - "bitrate": f"{bitrate} Kbit/s" if bitrate else "Unknown", - "codec": codec_name - }) - elif codec_type == "audio": - media_info["audio"].append({ - "bitrate": f"{bitrate} Kbit/s" if bitrate else "Unknown", - "channels": channels, - "layout": channel_layout, - "codec": codec_name - }) - - return media_info - - except Exception as e: - print(f"Error processing file {file_path}: {e}") - return None - - -# Function to list media files -def list_media_files(directories): - media_files = [] - for directory in directories: - if os.path.exists(directory): - for root, _, files in os.walk(directory): - for file in files: - file_path = os.path.join(root, file) - try: - file_info = { - "name": file, - "path": file_path, - "size": os.path.getsize(file_path) - } - media_files.append(file_info) - except OSError as e: - print(f"Error accessing file {file_path}: {e}") - else: - print(f"Directory does not exist: {directory}") - return media_files - -def filter_media_files(media_files, allowed_formats): - """ - Фильтрует список файлов по допустимым форматам. - - :param media_files: Список файлов, полученный из list_media_files. - :param allowed_formats: Список допустимых форматов, например [".mp3", ".jpg"]. - :return: Отфильтрованный список файлов. - """ - filtered_files = [ - file for file in media_files - if any(file['name'].lower().endswith(fmt.lower()) for fmt in allowed_formats) - ] - return filtered_files - -def human_readable_size(size_bytes): - if size_bytes < 1024: - return f"{size_bytes} B" - elif size_bytes < 1024**2: - return f"{size_bytes / 1024:.2f} KB" - elif size_bytes < 1024**3: - return f"{size_bytes / 1024**2:.2f} MB" - else: - return f"{size_bytes / 1024**3:.2f} GB" - @app.route('/') def index(): - # Получаем список медиафайлов + return render_template('index.html') + +@app.route('/media-list', methods=['GET']) +def media_list(): media_files = list_media_files(config.get("directories", [])) - # Фильтруем медиафайлы по формату allowed_formats = config.get("allowed_formats", []) filtered_files = filter_media_files(media_files, allowed_formats) + + # Add detailed media info for file in filtered_files: - file["size"] = human_readable_size(file["size"]) - # file["info"] = get_media_info(file["path"]) - file["info"] = get_media_info_with_ffprobe(file["path"]) - return render_template('index.html', media_files=filtered_files) + file["size"], file["size_unit"] = human_readable_size(file["size"]) + file_info = get_media_info_with_ffprobe(file["path"]) + if file_info: + file["info"] = file_info + else: + file["info"] = {"error": "Failed to retrieve media info"} + + return jsonify(filtered_files) @app.route('/configure', methods=['GET', 'POST']) def configure(): if request.method == 'POST': directories = request.json.get('directories', []) + allowed_formats = request.json.get('allowed_formats', []) config['directories'] = directories + config['allowed_formats'] = allowed_formats save_config(config) return jsonify({"status": "success"}) return jsonify(config) diff --git a/mediascan.py b/mediascan.py new file mode 100644 index 0000000..1198729 --- /dev/null +++ b/mediascan.py @@ -0,0 +1,134 @@ +import os +import json +import ffmpeg +import subprocess + +def get_media_info_with_ffprobe(file_path): + try: + if not os.path.exists(file_path): + print(f"File not found: {file_path}") + return None + + # Command to fetch streams + streams_command = [ + "ffprobe", "-v", "error", + "-show_entries", "stream=index,codec_type,codec_name,width,height,bit_rate,channels,channel_layout", + "-show_entries", "stream_tags=language", + "-of", "json", file_path + ] + + # Command to fetch format information + format_command = [ + "ffprobe", "-v", "error", + "-show_entries", "format=duration,bit_rate,size", + "-of", "json", file_path + ] + + streams_result = subprocess.run(streams_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + format_result = subprocess.run(format_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + + if streams_result.returncode != 0: + print(f"ffprobe stream error: {streams_result.stderr}") + return None + + if format_result.returncode != 0: + print(f"ffprobe format error: {format_result.stderr}") + return None + + streams_data = json.loads(streams_result.stdout) + format_data = json.loads(format_result.stdout) + + format_info = format_data.get("format", {}) + container_duration = float(format_info.get("duration", 0)) + container_bitrate = int(format_info.get("bit_rate", 0)) // 1000 if format_info.get("bit_rate") else "Unknown" + file_size = int(format_info.get("size", 0)) + + media_info = { + "video": [], + "audio": [], + "container": { + "duration": f"{container_duration:.2f} seconds" if container_duration else "Unknown", + "bitrate": f"{container_bitrate} Kbit/s" if container_bitrate else "Unknown", + "size": f"{file_size / (1024**2):.2f} MB" if file_size else "Unknown" + } + } + + for stream in streams_data.get("streams", []): + codec_type = stream.get("codec_type") + codec_name = stream.get("codec_name") + stream_bitrate = int(stream.get("bit_rate", 0)) // 1000 if stream.get("bit_rate") else None + width = stream.get("width") + height = stream.get("height") + channels = stream.get("channels", "Unknown") + channel_layout = stream.get("channel_layout", "Unknown") + language = stream.get("tags", {}).get("language", "Unknown") + bitrate = stream_bitrate if stream_bitrate else container_bitrate + + if codec_type == "video": + media_info["video"].append({ + "resolution": f"{width}x{height}" if width and height else "Unknown", + "bitrate": f"{bitrate} Kbit/s" if bitrate else "Unknown", + "codec": codec_name + }) + elif codec_type == "audio": + media_info["audio"].append({ + "bitrate": f"{stream_bitrate} Kbit/s" if stream_bitrate else "Unknown", + "channels": channels, + "layout": channel_layout, + "language": language, + "codec": codec_name + }) + + return media_info + + except Exception as e: + print(f"Error processing file {file_path}: {e}") + return None + + + + +# Function to list media files +def list_media_files(directories): + media_files = [] + for directory in directories: + if os.path.exists(directory): + for root, _, files in os.walk(directory): + for file in files: + file_path = os.path.join(root, file) + try: + file_info = { + "name": file, + "path": file_path, + "size": os.path.getsize(file_path) + } + media_files.append(file_info) + except OSError as e: + print(f"Error accessing file {file_path}: {e}") + else: + print(f"Directory does not exist: {directory}") + return media_files + +def filter_media_files(media_files, allowed_formats): + """ + Фильтрует список файлов по допустимым форматам. + + :param media_files: Список файлов, полученный из list_media_files. + :param allowed_formats: Список допустимых форматов, например [".mp3", ".jpg"]. + :return: Отфильтрованный список файлов. + """ + filtered_files = [ + file for file in media_files + if any(file['name'].lower().endswith(fmt.lower()) for fmt in allowed_formats) + ] + return filtered_files + +def human_readable_size(size_bytes): + if size_bytes < 1024: + return [f"{size_bytes}", "B"] + elif size_bytes < 1024**2: + return [f"{size_bytes / 1024:.2f}", "KB"] + elif size_bytes < 1024**3: + return [f"{size_bytes / 1024**2:.2f}", "MB"] + else: + return [f"{size_bytes / 1024**3:.2f}", "GB"] \ No newline at end of file diff --git a/static/js/media.js b/static/js/media.js new file mode 100644 index 0000000..7b00b77 --- /dev/null +++ b/static/js/media.js @@ -0,0 +1,54 @@ +// Function to load media data +function loadMediaList() { + $.ajax({ + url: "/media-list", + method: "GET", + success: function(data) { + const tableBody = $("#media-table tbody"); + tableBody.empty(); // Clear existing rows + + if (data.length > 0) { + data.forEach((file, index) => { + const videoInfo = file.info.video + ? file.info.video.map(v => `
  • Resolution: ${v.resolution}, Codec: ${v.codec}, Bitrate: ${v.bitrate}
  • `).join("") + : "
  • No video streams
  • "; + + const audioInfo = file.info.audio + ? file.info.audio.map(a => `
  • Channels: ${a.layout}, Codec: ${a.codec}, Bitrate: ${a.bitrate}, Language: ${a.language}
  • `).join("") + : "
  • No audio streams
  • "; + + const details = ` + Video: + + Audio: + + `; + + tableBody.append(` + + ${index + 1} + +
    ${file.name}
    +
    ${file.path}
    +
    ${details}
    + + ${file.size} + + `); + }); + } else { + tableBody.append(`No files found`); + } + + $('#media-table').DataTable(); + }, + error: function(xhr, status, error) { + console.error("Error loading media list:", error); + } + }); +} + +// Load media list when the page is ready +$(document).ready(function() { + loadMediaList(); +}); \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index b87643e..949e3c7 100644 --- a/templates/index.html +++ b/templates/index.html @@ -5,69 +5,27 @@ Media Library Manager + + + +

    Media Library Manager

    Configure Directories - +
    - + - {% if media_files %} - {% for file in media_files %} - - - - - - {% endfor %} - {% else %} - - - - {% endif %} +
    #File NameFile Size
    {{ loop.index }} -
    {{ file.name }}
    -
    {{ file.path }}
    - {% if file.info.video %} - Container: -
      -
    • Duration: {{ file.info.container.duration }}
    • -
    • Bitrate: {{ file.info.container.bitrate }}
    • -
    • Size: {{ file.info.container.size }}
    • -
    - -
      - {% for video in file.info.video %} -
    • Resolution: {{ video.resolution }}, Bitrate: {{ video.bitrate }} Kbit/s, Codec: {{ video.codec }}
    • - {% endfor %} -
    - {% endif %} - {% if file.info.audio %} -
      - {% for audio in file.info.audio %} -
    • Audio: {{ audio.layout }}, Bitrate: {{ audio.bitrate }} Kbit/s, Codec: {{ audio.codec }}, Language: {{ audio.language }}
    • - {% endfor %} -
    - {% endif %} -
    {{ file.size }}
    No files found in the configured directories.
    - - - - - -