|
|
@ -17,6 +17,37 @@ def get_video_id(url): |
|
|
|
return match.group(1) if match else None |
|
|
|
return match.group(1) if match else None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_comments(youtube, video_id): |
|
|
|
|
|
|
|
# Fetch comments for the video |
|
|
|
|
|
|
|
comments = [] |
|
|
|
|
|
|
|
try: |
|
|
|
|
|
|
|
response = youtube.commentThreads().list( |
|
|
|
|
|
|
|
part="snippet", |
|
|
|
|
|
|
|
videoId=video_id, |
|
|
|
|
|
|
|
textFormat="plainText", |
|
|
|
|
|
|
|
maxResults=100 # Adjust based on needs |
|
|
|
|
|
|
|
).execute() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while response: |
|
|
|
|
|
|
|
for item in response['items']: |
|
|
|
|
|
|
|
comment = item['snippet']['topLevelComment']['snippet']['textDisplay'] |
|
|
|
|
|
|
|
comments.append(comment) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if 'nextPageToken' in response: |
|
|
|
|
|
|
|
response = youtube.commentThreads().list( |
|
|
|
|
|
|
|
part="snippet", |
|
|
|
|
|
|
|
videoId=video_id, |
|
|
|
|
|
|
|
textFormat="plainText", |
|
|
|
|
|
|
|
pageToken=response['nextPageToken'], |
|
|
|
|
|
|
|
maxResults=100 # Adjust based on needs |
|
|
|
|
|
|
|
).execute() |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
break |
|
|
|
|
|
|
|
except HttpError as e: |
|
|
|
|
|
|
|
print(f"Failed to fetch comments: {e}") |
|
|
|
|
|
|
|
return comments |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main_function(url, options): |
|
|
|
def main_function(url, options): |
|
|
|
# Load environment variables from .env file |
|
|
|
# Load environment variables from .env file |
|
|
|
load_dotenv(os.path.expanduser("~/.config/fabric/.env")) |
|
|
|
load_dotenv(os.path.expanduser("~/.config/fabric/.env")) |
|
|
@ -38,9 +69,8 @@ def main_function(url, options): |
|
|
|
youtube = build("youtube", "v3", developerKey=api_key) |
|
|
|
youtube = build("youtube", "v3", developerKey=api_key) |
|
|
|
|
|
|
|
|
|
|
|
# Get video details |
|
|
|
# Get video details |
|
|
|
video_response = ( |
|
|
|
video_response = youtube.videos().list( |
|
|
|
youtube.videos().list(id=video_id, part="contentDetails").execute() |
|
|
|
id=video_id, part="contentDetails").execute() |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Extract video duration and convert to minutes |
|
|
|
# Extract video duration and convert to minutes |
|
|
|
duration_iso = video_response["items"][0]["contentDetails"]["duration"] |
|
|
|
duration_iso = video_response["items"][0]["contentDetails"]["duration"] |
|
|
@ -50,41 +80,51 @@ def main_function(url, options): |
|
|
|
# Get video transcript |
|
|
|
# Get video transcript |
|
|
|
try: |
|
|
|
try: |
|
|
|
transcript_list = YouTubeTranscriptApi.get_transcript(video_id) |
|
|
|
transcript_list = YouTubeTranscriptApi.get_transcript(video_id) |
|
|
|
transcript_text = " ".join([item["text"] |
|
|
|
transcript_text = " ".join([item["text"] for item in transcript_list]) |
|
|
|
for item in transcript_list]) |
|
|
|
|
|
|
|
transcript_text = transcript_text.replace("\n", " ") |
|
|
|
transcript_text = transcript_text.replace("\n", " ") |
|
|
|
except Exception as e: |
|
|
|
except Exception as e: |
|
|
|
transcript_text = f"Transcript not available. ({e})" |
|
|
|
transcript_text = f"Transcript not available. ({e})" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Get comments if the flag is set |
|
|
|
|
|
|
|
comments = [] |
|
|
|
|
|
|
|
if options.comments: |
|
|
|
|
|
|
|
comments = get_comments(youtube, video_id) |
|
|
|
|
|
|
|
|
|
|
|
# Output based on options |
|
|
|
# Output based on options |
|
|
|
if options.duration: |
|
|
|
if options.duration: |
|
|
|
print(duration_minutes) |
|
|
|
print(duration_minutes) |
|
|
|
elif options.transcript: |
|
|
|
elif options.transcript: |
|
|
|
print(transcript_text) |
|
|
|
print(transcript_text) |
|
|
|
|
|
|
|
elif options.comments: |
|
|
|
|
|
|
|
print(json.dumps(comments, indent=2)) |
|
|
|
else: |
|
|
|
else: |
|
|
|
# Create JSON object |
|
|
|
# Create JSON object with all data |
|
|
|
output = {"transcript": transcript_text, |
|
|
|
output = { |
|
|
|
"duration": duration_minutes} |
|
|
|
"transcript": transcript_text, |
|
|
|
|
|
|
|
"duration": duration_minutes, |
|
|
|
|
|
|
|
"comments": comments |
|
|
|
|
|
|
|
} |
|
|
|
# Print JSON object |
|
|
|
# Print JSON object |
|
|
|
print(json.dumps(output)) |
|
|
|
print(json.dumps(output, indent=2)) |
|
|
|
except HttpError as e: |
|
|
|
except HttpError as e: |
|
|
|
|
|
|
|
print(f"Error: Failed to access YouTube API. Please check your YOUTUBE_API_KEY and ensure it is valid: {e}") |
|
|
|
print( |
|
|
|
|
|
|
|
f"Error: Failed to access YouTube API. Please check your YOUTUBE_API_KEY and ensure it is valid: {e}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main(): |
|
|
|
def main(): |
|
|
|
parser = argparse.ArgumentParser( |
|
|
|
parser = argparse.ArgumentParser( |
|
|
|
description='yt (video meta) extracts metadata about a video, such as the transcript and the video\'s duration. By Daniel Miessler.') |
|
|
|
description='yt (video meta) extracts metadata about a video, such as the transcript, the video\'s duration, and now comments. By Daniel Miessler.') |
|
|
|
# Ensure 'url' is defined once |
|
|
|
|
|
|
|
parser.add_argument('url', help='YouTube video URL') |
|
|
|
parser.add_argument('url', help='YouTube video URL') |
|
|
|
parser.add_argument('--duration', action='store_true', |
|
|
|
parser.add_argument('--duration', action='store_true', help='Output only the duration') |
|
|
|
help='Output only the duration') |
|
|
|
parser.add_argument('--transcript', action='store_true', help='Output only the transcript') |
|
|
|
parser.add_argument('--transcript', action='store_true', |
|
|
|
parser.add_argument('--comments', action='store_true', help='Output the comments on the video') |
|
|
|
help='Output only the transcript') |
|
|
|
|
|
|
|
args = parser.parse_args() |
|
|
|
args = parser.parse_args() |
|
|
|
|
|
|
|
|
|
|
|
if args.url is None: |
|
|
|
if args.url is None: |
|
|
|
args.url = sys.stdin.readline().strip() |
|
|
|
print("Error: No URL provided.") |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
|
|
|
|
main_function(args.url, args) |
|
|
|
main_function(args.url, args) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
|
|
main() |
|
|
|